Skip to content

Refactor admin#14

Open
adil-khursheed wants to merge 3 commits intomainfrom
refactor-admin
Open

Refactor admin#14
adil-khursheed wants to merge 3 commits intomainfrom
refactor-admin

Conversation

@adil-khursheed
Copy link

@adil-khursheed adil-khursheed commented Mar 10, 2026

Summary by CodeRabbit

  • New Features

    • Added comprehensive institute creation form with address, contact, and subject management.
    • Added institute dashboard with overview, student, and teacher management sections.
    • Added institute selector dropdown in main navigation.
    • Added new UI components including avatars, badges, tabs, and tooltips.
    • Integrated enhanced data fetching and caching system.
  • Improvements

    • Enhanced authentication and cookie token handling.
    • Improved form validation across all user inputs.
    • Refined UI styling with updated design system.

…thentication, and data management for teachers, institutes, and students.
…pages, institute creation, and student enrollment features.
@beetle-ai
Copy link

beetle-ai bot commented Mar 10, 2026

Summary by Beetle

This PR represents a comprehensive refactoring and modernization of the admin web application, introducing significant architectural improvements, UI/UX enhancements, and new features for institute management. The changes span across three major commits that progressively build upon each other to create a robust admin dashboard system.

📁 File Changes Summary (Consolidated across all commits):

File Status Changes Description
.prettierignore
.prettierrc
eslint.config.mjs
Added +46/-0 Code Quality Setup: Introduced Prettier configuration with Tailwind CSS plugin and custom import ordering, plus ESLint configuration for Next.js with TypeScript support
package.json
package-lock.json
Modified +1088/-511 Dependency Upgrades: Major updates including React 19, Next.js 16, Tailwind CSS v4, Radix UI components, TanStack Query, React Hook Form, Zod validation, and Base UI components
postcss.config.mjs
tailwind.config.ts
Modified/Removed +1/-155 Styling Migration: Migrated from Tailwind CSS v3 to v4 with new PostCSS configuration using @tailwindcss/postcss
src/app/globals.css Modified +224/-106 Design System Overhaul: Implemented comprehensive CSS custom properties for colors, shadows, spacing, and typography with dark mode support
src/actions/cookie_actions.ts
src/actions/user_actions.ts
src/actions/institute_actions.ts
src/actions/batch_actions.ts
src/actions/student_action.ts
src/actions/mentor_actions.ts
Modified +127/-71 Server Actions Refactoring: Updated cookie handling to use default admin token name, improved error handling, added caching with React cache API, and enhanced type safety
src/app/api/auth/login/route.ts
src/app/api/auth/logout/route.ts
src/app/api/auth/verify/route.ts
src/app/api/google/auth/route.ts
Modified +57/-36 Authentication Enhancement: Standardized cookie naming to leadlly.in_admin_token, improved error handling with AxiosError types, and added proper logout flow
src/app/(auth)/login/page.tsx
src/app/(auth)/_components/GoogleLoginButton.tsx
Modified +48/-35 Login UI Modernization: Redesigned login page with Card components, Avatar for branding, and improved Google OAuth flow with better error handling
src/app/(protected)/layout.tsx
src/app/(protected)/(root)/layout.tsx
src/app/provider.tsx
src/app/QueryProvider.tsx
Added +119/-0 Protected Route Architecture: Implemented layered layout system with authentication checks, React Query provider setup, and Redux store integration
src/app/(protected)/create-institute/page.tsx
src/app/(protected)/create-institute/_components/create-institute-form.tsx
Modified/Added +577/-324 Institute Creation Feature: Complete form implementation with React Hook Form, Zod validation, multi-step fields for institute details, subjects, standards, and address information
src/app/(protected)/(root)/(dashboard)/page.tsx
src/app/(protected)/(root)/institute/[instituteId]/page.tsx
Modified +75/-121 Dashboard Refactoring: Simplified dashboard with institute overview, student/teacher management, and dynamic routing based on active institute
src/app/(protected)/(root)/_components/main-header.tsx
src/app/(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx
src/app/(protected)/(root)/institute/[instituteId]/_components/add-students-dialog.tsx
Added +278/-0 Institute Management UI: Header with institute switcher dropdown, detailed institute overview with contact information, and student enrollment dialog
src/components/ui/button.tsx
src/components/ui/input.tsx
src/components/ui/checkbox.tsx
src/components/ui/dropdown-menu.tsx
Modified +58/-43 Component Library Updates: Enhanced button variants with new sizes and states, improved input styling, better checkbox alignment, and dropdown menu scrolling
src/components/ui/combobox.tsx
src/components/ui/field.tsx
src/components/ui/input-group.tsx
src/components/ui/avatar.tsx
src/components/ui/badge.tsx
src/components/ui/tabs.tsx
src/components/ui/tooltip.tsx
src/components/ui/separator.tsx
src/components/ui/scroll-area.tsx
src/components/ui/switch.tsx
src/components/ui/skeleton.tsx
src/components/ui/spinner.tsx
src/components/ui/button-group.tsx
Added +1,000+/-0 New UI Components: Comprehensive set of reusable components built on Base UI and Radix UI primitives for forms, navigation, and data display
src/helpers/schema/createInstituteSchema.ts
src/helpers/types/index.ts
Added/Modified +77/-2 Type Safety & Validation: Zod schema for institute creation form validation and enhanced TypeScript interfaces for Admin and Institute entities
src/redux/slices/userSlice/index.ts
src/redux/slices/instituteSlice/index.ts
src/app/StoreProvider.tsx
Modified +19/-12 State Management Improvements: Added proper TypeScript typing with PayloadAction, updated user/institute state to use new IAdmin/IInstitute interfaces
src/middleware.ts
src/proxy.ts
Removed/Added +48/-28 Routing Logic Enhancement: Replaced middleware with proxy for better authentication flow, automatic redirection based on institute membership, and improved path matching

Total Changes: 73 files changed, +3,500 additions, -1,800 deletions

🗺️ Walkthrough:

sequenceDiagram
participant User
participant Login
participant Auth
participant Protected
participant Institute
participant Dashboard
User->>Login: Access Application
Login->>Auth: Google OAuth / Email Login
Auth->>Auth: Set leadlly.in_admin_token
alt Has Institutes
Auth->>Protected: Redirect to /institute/:id
Protected->>Institute: Load Active Institute
Institute->>Dashboard: Display Overview
else No Institutes
Auth->>Protected: Redirect to /create-institute
Protected->>Institute: Show Creation Form
Institute->>Institute: Validate with Zod Schema
Institute->>Dashboard: Create & Redirect
end
Dashboard->>Dashboard: Fetch Institute Data (TanStack Query)
Dashboard->>Dashboard: Display Students/Teachers
Note over Dashboard: User can switch institutes via header dropdown
Dashboard->>Institute: Switch Institute
Institute->>Dashboard: Update Active Institute
Loading

🎯 Key Changes:

1. Major Framework & Dependency Upgrades

  • React 19 & Next.js 16: Upgraded to latest versions with improved performance and new features
  • Tailwind CSS v4: Complete migration to new architecture with CSS-first configuration
  • TanStack Query: Introduced for server state management with prefetching and caching
  • React Hook Form + Zod: Implemented type-safe form validation and management

2. Authentication & Authorization Enhancements

  • Standardized authentication token naming (leadlly.in_admin_token)
  • Improved OAuth flow with better error handling and user feedback
  • Enhanced routing logic with automatic redirection based on institute membership
  • Added server-side authentication checks in protected layouts

3. Institute Management System

  • Multi-Institute Support: Users can manage multiple institutes with easy switching
  • Comprehensive Creation Form: Detailed institute setup with subjects, standards, and contact information
  • Dynamic Routing: Institute-specific pages with proper data fetching and caching
  • Student Enrollment: Dialog-based interface for adding students via email

4. UI/UX Improvements

  • Design System: Implemented comprehensive CSS custom properties for consistent theming
  • Component Library: Added 13+ new reusable UI components (Combobox, Tabs, Tooltip, etc.)
  • Responsive Design: Enhanced mobile-first approach with better breakpoint handling
  • Dark Mode Support: Full dark mode implementation with theme variables

5. Code Quality & Developer Experience

  • TypeScript Strictness: Enhanced type safety across all modules
  • Code Formatting: Prettier configuration with automatic import sorting
  • Error Boundaries: Added error handling for better user experience
  • Suspense Boundaries: Implemented loading states for async components

6. Performance Optimizations

  • React Cache API: Utilized for server-side data caching
  • Query Prefetching: Implemented with TanStack Query for faster page loads
  • Code Splitting: Improved with dynamic imports and lazy loading
  • Optimized Re-renders: Better state management with Redux and React Query
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only.Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

Follow us: Beetle · X · LinkedIn

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

This pull request modernizes the application architecture by integrating React Query for server-side data fetching, implementing a proxy-based authentication middleware, renaming the authentication cookie, expanding the UI component library significantly, migrating from Tailwind config to CSS-driven tokens, and restructuring authentication flows with new admin API endpoints and cache invalidation patterns.

Changes

Cohort / File(s) Summary
Configuration & Build System
.prettierignore, .prettierrc, eslint.config.mjs, postcss.config.mjs, tsconfig.json
Added Prettier ignore rules; introduced Prettier config with TailwindCSS and import-sort plugins; created flat ESLint config; updated PostCSS to use @tailwindcss/postcss; expanded TypeScript config with react-jsx and multi-line formatting.
Package Dependencies
package.json
Updated Next.js (16.1.6), React (19.2.4), added @tanstack/react-query, @hookform/resolvers, @base-ui/react, zod, next-theme; added dev dependencies @tailwindcss/postcss, @trivago/prettier-plugin-sort-imports; added type overrides for React packages; replaced several Radix UI packages with expanded ecosystem.
Styling System
src/app/globals.css, tailwind.config.ts
Migrated from Tailwind config-based system to CSS-driven token approach with extensive CSS variables, dark mode variants, custom utilities (scrollbar, container), and @theme block; removed tailwind.config.ts entirely.
Authentication & Cookies
src/app/api/auth/...*, src/actions/cookie_actions.ts
Renamed authentication cookie from "token" to "leadlly.in_admin_token" across all auth routes; updated logout to call external admin API; added getCookie() default parameter with default token name; removed old src/middleware.ts; added new src/proxy.ts middleware with institute-based routing logic.
React Query Integration
src/app/QueryProvider.tsx, src/app/(protected)/layout.tsx, src/app/(root)/layout.tsx
Introduced QueryProvider with server/client-aware QueryClient initialization and 60-second staleTime; added data prefetching in protected layout via HydrationBoundary; added query client setup in root layout.
Action Layer Refactoring
src/actions/batch_actions.ts, src/actions/mentor_actions.ts, src/actions/student_action.ts, src/actions/user_actions.ts, src/actions/institute_actions.ts
Replaced getCookie("token") with getCookie(); changed cache invalidation from revalidateTag to updateTag in user actions; updated error handling patterns; modified getUser to use cache() wrapper with force-cache and 60-minute revalidation; enhanced institute actions with getActiveInstitute function and updated endpoints to use /api/institute with isAdmin: "true" header.
UI Component Library
src/components/ui/avatar.tsx, src/components/ui/badge.tsx, src/components/ui/button.tsx, src/components/ui/button-group.tsx, src/components/ui/checkbox.tsx, src/components/ui/combobox.tsx, src/components/ui/dropdown-menu.tsx, src/components/ui/field.tsx, src/components/ui/input-group.tsx, src/components/ui/scroll-area.tsx, src/components/ui/separator.tsx, src/components/ui/skeleton.tsx, src/components/ui/sonner.tsx, src/components/ui/spinner.tsx, src/components/ui/switch.tsx, src/components/ui/tabs.tsx, src/components/ui/tooltip.tsx
Expanded component library with 17 new UI components using Radix UI primitives, class-variance-authority for variants, and design tokens; includes form components, layout primitives, and overlay components with accessibility attributes and responsive styling.
Authentication Pages
src/app/(auth)/login/page.tsx, src/app/(auth)/create-institute/page.tsx, src/app/(protected)/create-institute/_components/create-institute-form.tsx, src/app/(protected)/create-institute/page.tsx
Restructured login page with Card-based layout and Avatar; removed old create-institute page from auth flow; added new create-institute form component in protected route with zod validation and Redux integration; updated Google login flow to use Axios and remove Redux dispatch; adjusted post-login navigation based on institute status.
Protected Routes & Layouts
src/app/(protected)/layout.tsx, src/app/(root)/layout.tsx, src/app/layout.tsx, src/app/StoreProvider.tsx, src/app/provider.tsx
Added protected layout with authentication check via getCookie(); created new root layout with MainHeader integration and React Query prefetching; updated main layout to use Google Font (Mada), removed user data fetching from root layout; enhanced StoreProvider with IAdmin type and explicit null handling.
Institute Management Pages
src/app/(protected)/(root)/institute/[instituteId]/page.tsx, src/app/(protected)/(root)/(dashboard)/page.tsx, src/app/(root)/institute/[instituteId]/page.tsx
Added new institute dashboard page with QueryClient prefetching, ErrorBoundary/Suspense composition, mock data, and AddStudentsDialog integration; commented out add-teachers UI; removed old auth-protected institute page; updated dashboard to fetch active institute data.
Header & Navigation Components
src/app/(protected)/(root)/_components/main-header.tsx, src/app/(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx, src/app/(protected)/(root)/institute/[instituteId]/_components/StudentsOverview.tsx, src/app/(protected)/(root)/institute/[instituteId]/_components/add-students-dialog.tsx
Created new MainHeader component with institute dropdown and dialog-based add-institute form; refactored InstituteOverview to accept instituteId and fetch data via React Query; updated StudentsOverview props from nested params object to flat instituteId; added new AddStudentsDialog with form validation and commented-out submission logic.
Student/Teacher Pages
src/app/(protected)/(root)/institute/[instituteId]/batches/[batchId]/students/page.tsx, src/app/(protected)/(root)/teachers/page.tsx, src/app/(protected)/(root)/teacher/[teacherId]/_components/TeacherHeader.tsx, src/app/(protected)/(root)/teacher/[teacherId]/_components/TeacherStats.tsx
Updated button variants from "outline" to "outline-solid" for filter states; changed focus outline from none to hidden; reduced shadow strength from shadow-sm to shadow-xs on teacher components; adjusted CSS gradient class notation.
Type System & Schemas
src/helpers/types/index.ts, src/helpers/schema/createInstituteSchema.ts, src/redux/slices/userSlice/index.ts, src/redux/slices/instituteSlice/index.ts
Added comprehensive IAdmin and IInstitute types with nested structures and permissions; tightened Redux slice types from any to IAdmin | null and IInstitute | null; added CreateInstituteFormSchema with zod validation for institute fields; updated type contracts in PayloadAction signatures.
Component Cleanup
src/app/(root)/institute/_components/index.ts
Removed barrel exports for StudentsOverview, TeachersOverview, and InstituteOverview from legacy location.
Google Authentication
src/app/(auth)/_components/GoogleLoginButton.tsx, src/app/api/google/auth/route.ts
Refactored Google login with loading state, full Axios config, and admin flag in token exchange; updated error handling with AxiosError typing; changed navigation logic based on response status; updated icon asset path; enhanced API route with improved error distinction and cookie handling.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Browser
    participant Proxy as Proxy Middleware
    participant Auth as Auth Endpoint
    participant User as User Action
    participant Query as QueryClient
    participant AdminAPI as Admin API

    Client->>Proxy: Request protected route
    Proxy->>Proxy: Read admin token from cookies
    alt Token exists
        Proxy->>User: Call getUser()
        User->>Query: Check cache (if available)
        alt Cache hit
            Query-->>User: Return cached user
        else Cache miss
            User->>AdminAPI: GET /api/admin/get (with token)
            AdminAPI-->>User: Return { success, admin }
            User->>Query: Store in cache (60s revalidate)
        end
        User-->>Proxy: Return user.admin
        alt User on login page
            Proxy->>Client: Redirect to institute page
        else User no institutes
            Proxy->>Client: Redirect to /create-institute
        else Allow request
            Proxy->>Client: NextResponse.next()
        end
    else No token
        Proxy->>Client: Redirect to /login
    end
Loading
sequenceDiagram
    participant User as User
    participant GoogleBtn as GoogleLoginButton
    participant GoogleAPI as Google OAuth
    participant TokenAPI as /api/google/auth
    participant AdminAPI as Admin API
    participant Router as Next Router

    User->>GoogleBtn: Click login
    GoogleBtn->>GoogleAPI: initiate OAuth flow
    GoogleAPI->>GoogleBtn: Return token
    GoogleBtn->>TokenAPI: POST with token + isAdmin flag
    TokenAPI->>AdminAPI: Validate & exchange token
    AdminAPI-->>TokenAPI: Return { status, data: { success, admin } }
    TokenAPI->>TokenAPI: Set leadlly.in_admin_token cookie
    TokenAPI-->>GoogleBtn: Return response
    alt status === 201 (new institute)
        GoogleBtn->>Router: navigate /create-institute
    else Has institutes
        GoogleBtn->>Router: navigate /
    else No institutes
        GoogleBtn->>Router: navigate /create-institute
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 Whiskers twitch with joy unbounded,
Cache invalidation bugs confounded!
React Query hops through data streams,
Tokens dance in cookie dreams,
Components bloom like garden flowers,
This refactor grants new powers!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Refactor admin" is vague and generic, using non-descriptive terms that don't convey meaningful information about the specific changes in the changeset. Use a more specific title that describes the primary change, such as "Migrate to admin API integration with React Query" or "Restructure authentication and institute management flows".
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-admin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

/.vscode
/node_modules
./dist
*. env
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue: Typo in ignore pattern *. env (space between * and .env) will NOT match .env files. This defeats the purpose of ignoring environment files that typically contain sensitive credentials and secrets. Environment files could be formatted by Prettier and potentially exposed in version control.

Confidence: 5/5

Suggested Fix
Suggested change
*. env
*.env

Remove the space between * and .env to properly match all .env files.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In .prettierignore at line 4, there is a typo in the ignore pattern "*. env" which has
a space between the asterisk and ".env". This will not match .env files as intended.
Fix this by removing the space to make it "*.env" so that all environment files
containing sensitive credentials are properly ignored by Prettier.

./dist
*. env
.env
.env .* No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue: Typo in ignore pattern .env .* (space between .env and .*) will NOT match environment variant files like .env.local, .env.production, .env.development. These files typically contain environment-specific secrets and credentials. The typo defeats the security purpose of ignoring these sensitive files.

Confidence: 5/5

Suggested Fix
Suggested change
.env .*
.env.*

Remove the space between .env and .* to properly match all environment variant files.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In .prettierignore at line 6, there is a typo in the ignore pattern ".env .*" which has
a space between ".env" and ".*". This will not match environment variant files like
.env.local, .env.production, etc. as intended. Fix this by removing the space to make
it ".env.*" so that all environment variant files containing sensitive credentials are
properly ignored by Prettier.

import { cookies } from "next/headers";

export const getCookie = async (name: string) => {
export const getCookie = async (name = "leadlly.in_admin_token") => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue: Hardcoding the admin authentication token cookie name "leadlly.in_admin_token" as a default parameter exposes sensitive authentication implementation details. This makes it easier for attackers to:

  • Target the specific cookie in session hijacking attacks
  • Craft CSRF attacks knowing the exact cookie name
  • Perform cookie theft or manipulation attacks
    The hardcoded default also reduces security flexibility - if the cookie name needs to be changed after a security incident, all implicit usages must be identified and updated.

Confidence: 4/5

Recommended Action

Consider one of these approaches:

  1. Remove the default parameter - Require explicit cookie name at all call sites for better security awareness and control
  2. Use environment variable - Store the cookie name in an environment variable (e.g., process.env.ADMIN_TOKEN_COOKIE_NAME) to avoid hardcoding
  3. Use a centralized constant - Define the cookie name in a secure configuration file that's not committed to version control
    If keeping the default, ensure:
  • The cookie has HttpOnly, Secure, and SameSite=Strict flags set
  • Cookie name rotation strategy is documented
  • All usages are audited for security implications
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/cookie_actions.ts at line 5, the getCookie function now has a hardcoded
default parameter "leadlly.in_admin_token" for the admin authentication cookie name.
This exposes sensitive authentication implementation details and makes it easier for
attackers to target this specific cookie. Remove the default parameter and require
explicit cookie names at all call sites, or move the cookie name to an environment
variable (process.env.ADMIN_TOKEN_COOKIE_NAME) to avoid hardcoding sensitive values.
Review all call sites to ensure they explicitly specify the cookie name for better
security awareness.

standards?: string[];
}
type InstituteCreateData = z.infer<typeof CreateInstituteFormSchema>;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue - Potential Authentication Bypass: The change from getCookie("token") to getCookie() now relies on the hardcoded default parameter "leadlly.in_admin_token" (as identified in Comment #4 on cookie_actions.ts).
Critical Problem: If the actual authentication cookie name is "token" (as the original code indicates), this change will cause getCookie() to look for the wrong cookie name ("leadlly.in_admin_token" instead of "token"). This means:

  • token variable will be undefined or null
  • The Cookie: token=${token} header will be malformed (e.g., Cookie: token=undefined)
  • All API requests will fail authentication or bypass authentication checks entirely
  • This affects ALL institute operations: createInstitute, getMyInstitute, getAllUserInstitutes
    Verify immediately: Check what the actual cookie name is in production. If it's "token", this is a breaking change that will cause complete authentication failure for all institute-related API calls.

Confidence: 5/5

Recommended Action

Immediate verification needed:

  1. Check the actual cookie name set during authentication (in login/auth routes)
  2. If the cookie name is "token", revert these changes and pass "token" explicitly
  3. If the cookie name is "leadlly.in_admin_token", then the original code was buggy and this fix is correct
    Recommended fix (if cookie name is actually "token"):
const token = await getCookie("token");

Or better yet, define a constant for the cookie name:

const AUTH_COOKIE_NAME = process.env.AUTH_COOKIE_NAME || "token";
const token = await getCookie(AUTH_COOKIE_NAME);

This same issue affects lines 39, 66, and 93 (commented code) in this file.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/institute_actions.ts at lines 13, 39, and 66, the code was changed from
getCookie("token") to getCookie() which now relies on a hardcoded default parameter
"leadlly.in_admin_token". This is a critical security issue because if the actual
authentication cookie name is "token" (as the original code suggests), all these API
calls will fail to retrieve the authentication token, causing authentication bypass.
Verify the actual cookie name used in production immediately. If it's "token", revert
to getCookie("token"). If it's "leadlly.in_admin_token", document this change as a bug
fix. Consider using an environment variable for the cookie name instead of hardcoding.

instituteId: string,
emails: string[]
) {
try {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same authentication issue as Comment #5 - The change from getCookie("token") to getCookie() creates the same potential authentication bypass vulnerability identified in institute_actions.ts. If the actual authentication cookie name is "token" (as the original code indicates), this will cause the addTeacherToInstitute function to fail authentication, as it will look for the wrong cookie name ("leadlly.in_admin_token" instead of "token").
This affects the API call on line 45 where Cookie: token=${token} will be malformed if token is undefined.

Confidence: 5/5

Suggested Fix
Suggested change
try {
const token = await getCookie("token");

Revert to explicitly passing the cookie name, or verify that the actual authentication cookie name is "leadlly.in_admin_token" and document this as a bug fix.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/mentor_actions.ts at line 17, the code was changed from getCookie("token")
to getCookie() which now relies on a hardcoded default parameter "leadlly.in_admin_token".
This is the same critical authentication issue identified in institute_actions.ts Comment #5.
Verify the actual authentication cookie name used in production. If it's "token", revert to
getCookie("token"). If it's "leadlly.in_admin_token", document this as a bug fix. Consider
using an environment variable for the cookie name to avoid hardcoding sensitive values.

instituteId: string,
emails: string[]
) {
try {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same authentication issue as Comments #5 and #6 - The change from getCookie("token") to getCookie() creates the same potential authentication bypass vulnerability identified in institute_actions.ts and mentor_actions.ts.
If the actual authentication cookie name is "token" (as the original code indicates), this will cause the addStudentsToInstitute function to fail authentication. The function will look for the wrong cookie name ("leadlly.in_admin_token" instead of "token"), resulting in:

  • token variable being undefined or null
  • Malformed Cookie: token=undefined header on line 45
  • Complete authentication failure for student addition operations
    This affects the API call to /api/student/add/${instituteId} which requires valid authentication.

Confidence: 5/5

Suggested Fix
Suggested change
try {
const token = await getCookie("token");

Revert to explicitly passing the cookie name. If the actual cookie name is "leadlly.in_admin_token", then document this as a bug fix and verify all authentication flows are working correctly.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/student_action.ts at line 17, the code was changed from getCookie("token")
to getCookie() which now relies on a hardcoded default parameter "leadlly.in_admin_token".
This is the same critical authentication issue identified in institute_actions.ts (Comment #5)
and mentor_actions.ts (Comment #6). Verify the actual authentication cookie name used in
production immediately. If it's "token", revert to getCookie("token"). If it's
"leadlly.in_admin_token", document this as a bug fix and verify all authentication flows.
Consider using an environment variable for the cookie name to avoid hardcoding sensitive values.


export const getUser = async () => {
const token = await getCookie("token");
export const getUser = cache(async () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same authentication issue as Comments #5, #6, and #7 - The change from getCookie("token") to getCookie() creates the same potential authentication bypass vulnerability identified in institute_actions.ts, mentor_actions.ts, and student_action.ts.
Critical Problem: This change affects the getUser function (now wrapped in React cache()), which is likely a core authentication function used throughout the application. If the actual authentication cookie name is "token" (as the original code indicates), this will cause:

  • token variable to be undefined or null
  • Malformed Cookie: token=undefined header on line 149
  • Complete authentication failure for user data retrieval
  • Cascading failures across the entire application since getUser is typically called to verify authentication state
    This same issue also affects:
  • Line 178: studentPersonalInfo function
  • Line 208: setTodaysVibe function
    Verify immediately: Check what the actual cookie name is in production. If it's "token", this is a breaking change that will cause complete authentication failure across all user-related operations.

Confidence: 5/5

Suggested Fix
Suggested change
export const getUser = cache(async () => {
const token = await getCookie("token");

Revert to explicitly passing the cookie name. If the actual cookie name is "leadlly.in_admin_token", then document this as a bug fix and verify all authentication flows are working correctly.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/user_actions.ts at lines 145, 178, and 208, the code was changed from
getCookie("token") to getCookie() which now relies on a hardcoded default parameter
"leadlly.in_admin_token". This is the same critical authentication issue identified in
Comments #5, #6, and #7 for other action files. The getUser function is particularly
critical as it's likely used throughout the application for authentication checks. Verify
the actual authentication cookie name used in production immediately. If it's "token",
revert all three instances to getCookie("token"). If it's "leadlly.in_admin_token",
document this as a bug fix and verify all authentication flows. Consider using an
environment variable for the cookie name to avoid hardcoding sensitive values.

setLoading(true);
// Dispatch institute data to Redux store
dispatch(instituteData(institute));
// dispatch(instituteData(institute));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Concern - Potential Authorization Bypass: Commenting out dispatch(instituteData(institute)) removes the Redux state storage for the selected institute. The code still navigates to /institute/${institute._id} (line 60), but without storing the institute data in Redux.
Security Risk: If the destination page (/institute/[instituteId]/page.tsx) relies on Redux state to verify that the user has legitimate access to the selected institute, this change breaks that authorization check. A malicious user could potentially:

  1. Navigate directly to /institute/{any-institute-id} via URL manipulation
  2. Bypass the institute selection flow entirely
  3. Access institutes they don't own if the destination page doesn't perform server-side authorization
    Critical Questions:
  • Does /institute/[instituteId]/page.tsx perform server-side authorization checks?
  • Or does it rely on Redux state to verify the user selected this institute legitimately?
    If authorization relies on client-side Redux state, this is a critical security vulnerability. If the destination page performs proper server-side authorization (checking that the authenticated user owns/has access to the requested institute ID), then this change is safe but removes useful client-side state management.

Confidence: 4/5

Recommended Action

Immediate verification needed:

  1. Review /institute/[instituteId]/page.tsx to confirm it performs server-side authorization
  2. Ensure the page validates that the authenticated user has access to the requested institute ID
  3. Do NOT rely on client-side Redux state for authorization decisions
    If keeping the commented code:
  • Document why Redux dispatch was removed
  • Ensure all institute pages perform server-side authorization checks
  • Consider removing the commented code entirely if it's no longer needed
    If Redux state is needed:
  • Uncomment lines 35 and 58 to restore Redux functionality
  • Or implement proper server-side authorization if not already present
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/(auth)/select-institute/page.tsx at lines 35 and 58, the Redux dispatch
functionality has been commented out. This means the selected institute data is no
longer stored in Redux state before navigating to /institute/${institute._id}. Verify
that the destination page (/institute/[instituteId]/page.tsx) performs proper
server-side authorization to check that the authenticated user has access to the
requested institute ID. If authorization relies on client-side Redux state, this is a
critical security vulnerability that enables authorization bypass via direct URL
manipulation. Either uncomment the Redux dispatch code or ensure robust server-side
authorization is in place. Document the reason for this change.

export const dynamic = "force-dynamic";

const ProtectedLayout = async ({ children }: { children: React.ReactNode }) => {
const token = await getCookie();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Security Issue - Authentication Gateway Failure: This line uses getCookie() without specifying the cookie name, relying on the hardcoded default parameter "leadlly.in_admin_token" from cookie_actions.ts (see Comment #4).
Why this is CRITICAL: This is the authentication gateway for ALL protected routes in the application. If the actual authentication cookie name is "token" (as indicated by the original code in Comments #5-#8), then:

  1. Check the actual cookie name set during login (in /api/auth/login/route.ts)
  2. If it's "token", this is a breaking change causing complete application lockout
  3. If it's "leadlly.in_admin_token", verify that all authentication flows use this name consistently

Confidence: 5/5

Suggested Fix
Suggested change
const token = await getCookie();
const token = await getCookie("token");

Explicitly specify the cookie name. If the actual cookie name is "leadlly.in_admin_token", then use that and document this as a bug fix. Better yet, use an environment variable:

const AUTH_COOKIE_NAME = process.env.AUTH_COOKIE_NAME || "token";
const token = await getCookie(AUTH_COOKIE_NAME);
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/(protected)/layout.tsx at line 10, the code uses getCookie() without
specifying the cookie name, relying on a hardcoded default "leadlly.in_admin_token".
This is the authentication gateway for ALL protected routes. If the actual
authentication cookie name is "token" (as suggested by original code in other files),
this will cause complete application lockout - no authenticated users can access any
protected routes. Verify the actual cookie name used in /api/auth/login/route.ts
immediately. If it's "token", change line 10 to getCookie("token"). If it's
"leadlly.in_admin_token", document this as a bug fix and verify all authentication
flows. Consider using an environment variable for the cookie name to avoid hardcoding.

res.cookies.set('token', '', {
await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Security Issue - Session Fixation Vulnerability: The logout route now clears a cookie named "leadlly.in_admin_token" instead of "token". This creates a critical security vulnerability if the actual authentication cookie name is "token" (as indicated by the original code).
Impact:

  1. What cookie name does /api/auth/login/route.ts actually set?
  2. If it sets "token", then this logout route will fail to clear it
  3. If it sets "leadlly.in_admin_token", then the original code was buggy
    Immediate verification required: Check the login route to confirm which cookie name is actually being set during authentication.

Confidence: 5/5

Suggested Fix
Suggested change
{
res.cookies.set("token", "", {

Revert to clearing the "token" cookie if that's what the login route sets. If the login route sets "leadlly.in_admin_token", then this change is correct but you must verify that ALL authentication flows (login, protected routes, API calls) consistently use "leadlly.in_admin_token".
Better approach: Use a centralized constant for the cookie name:

const AUTH_COOKIE_NAME = process.env.AUTH_COOKIE_NAME || "token";
res.cookies.set(AUTH_COOKIE_NAME, "", { ... });
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/api/auth/logout/route.ts at line 7, the logout route now clears a cookie
named "leadlly.in_admin_token" instead of "token". This is a critical security issue
because if the login route sets a cookie named "token", then logout will fail to clear
it, leaving users permanently logged in and creating a session fixation vulnerability.
Verify immediately what cookie name is set in src/app/api/auth/login/route.ts. If it's
"token", revert line 7 to res.cookies.set("token", "", {...}). If it's
"leadlly.in_admin_token", verify that ALL authentication flows (protected layout,
action files, API routes) consistently use this name. Consider using an environment
variable for the cookie name to avoid hardcoding and ensure consistency across the
codebase.

Comment on lines +15 to +19
res.cookies.set("leadlly.in_admin_token", token, {
httpOnly: true,
path: '/',
sameSite: 'strict',
expires: new Date('9999-12-31T23:59:59Z')
path: "/",
sameSite: "strict",
expires: new Date("9999-12-31T23:59:59Z"),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue - Hardcoded Cookie Name & Lack of Centralization: The authentication cookie name "leadlly.in_admin_token" is now hardcoded in this verify route. This same cookie name is hardcoded in multiple other files (logout route, cookie_actions.ts default parameter). If these values get out of sync during future maintenance, authentication will break across the application.
Additional Concern: The cookie expiration is set to year 9999 (essentially never expires). While this might be intentional for persistent sessions, it creates a security risk if:

  • The token itself doesn't have server-side expiration validation
  • Compromised tokens remain valid indefinitely
  • Users cannot force session termination across devices
    Recommendation: Centralize the cookie name in a single configuration constant to prevent synchronization issues:

Confidence: 4/5

Suggested Fix

Create a centralized auth configuration file (e.g., src/config/auth.ts):

export const AUTH_CONFIG = {
COOKIE_NAME: process.env.AUTH_COOKIE_NAME || "leadlly.in_admin_token",
COOKIE_MAX_AGE: 30 * 24 * 60 * 60, // 30 days in seconds
} as const;

Then update this file to use the centralized constant:

Suggested change
res.cookies.set("leadlly.in_admin_token", token, {
httpOnly: true,
path: '/',
sameSite: 'strict',
expires: new Date('9999-12-31T23:59:59Z')
path: "/",
sameSite: "strict",
expires: new Date("9999-12-31T23:59:59Z"),
res.cookies.set(AUTH_CONFIG.COOKIE_NAME, token, {
httpOnly: true,
path: "/",
sameSite: "strict",
maxAge: AUTH_CONFIG.COOKIE_MAX_AGE,
});

Benefits:

  • Single source of truth for cookie name
  • Easy to change cookie name if needed (security incident response)
  • Environment variable support for different deployments
  • More reasonable cookie expiration (30 days instead of forever)
    Important: Update all other files that reference this cookie name to use the same centralized constant:
  • src/actions/cookie_actions.ts (default parameter)
  • src/app/api/auth/logout/route.ts (cookie clearing)
  • src/app/(protected)/layout.tsx (authentication check)
  • All action files that call getCookie()
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/api/auth/verify/route.ts at lines 15-19, the authentication cookie name
"leadlly.in_admin_token" is hardcoded. This same value is hardcoded in multiple other
files (logout route, cookie_actions.ts), creating a maintenance and security risk if
they get out of sync. Create a centralized auth configuration file at
src/config/auth.ts that exports AUTH_CONFIG with COOKIE_NAME and COOKIE_MAX_AGE
constants. Update line 15 to use AUTH_CONFIG.COOKIE_NAME instead of the hardcoded
string. Also consider changing the cookie expiration from year 9999 to a more
reasonable value like 30 days using maxAge instead of expires. Then update all other
files that reference this cookie name to use the same centralized constant for
consistency and security.

📍 This suggestion applies to lines 15-19

Comment on lines 16 to +20
const res = NextResponse.json(userData);

res.cookies.set('token', token, {
res.cookies.set("leadlly.in_admin_token", token, {
httpOnly: true,
path: '/',
sameSite: 'strict',
expires: new Date('9999-12-31T23:59:59Z')
});
path: "/",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue - Hardcoded Cookie Name & Lack of Centralization: The authentication cookie name "leadlly.in_admin_token" is hardcoded here, matching the same hardcoded value in the verify route (Comment #12) and logout route (Comment #11). While this consistency is good, having the cookie name hardcoded in multiple files creates a maintenance and security risk.
Additional Security Concern: The cookie expiration is set to year 9999 (line 20), meaning the cookie essentially never expires. Combined with the lack of centralized configuration, if a security incident requires changing the cookie name or expiration policy, you'll need to update multiple files manually, increasing the risk of missing one and creating authentication inconsistencies.
Impact: If these hardcoded values get out of sync during future maintenance, authentication will break across the application. This is particularly risky for security-critical changes that need to be deployed quickly (e.g., responding to a credential leak).

Confidence: 4/5

Recommended Action

Create a centralized auth configuration file (e.g., src/config/auth.ts):

export const AUTH_CONFIG = {
COOKIE_NAME: process.env.AUTH_COOKIE_NAME || "leadlly.in_admin_token",
COOKIE_MAX_AGE: 30 * 24 * 60 * 60, // 30 days in seconds
} as const;

Then update this file to use the centralized constant:

import { AUTH_CONFIG } from "@/config/auth";
// In the POST handler:
res.cookies.set(AUTH_CONFIG.COOKIE_NAME, token, {
httpOnly: true,
path: "/",
sameSite: "strict",
maxAge: AUTH_CONFIG.COOKIE_MAX_AGE,
});

Benefits:

  • Single source of truth for cookie name and expiration
  • Easy to change cookie configuration if needed (security incident response)
  • Environment variable support for different deployments
  • More reasonable cookie expiration (30 days instead of forever)
    Important: Update all other files that reference this cookie name to use the same centralized constant:
  • src/app/api/auth/verify/route.ts (line 15)
  • src/app/api/auth/logout/route.ts (line 7)
  • src/actions/cookie_actions.ts (default parameter)
  • src/app/(protected)/layout.tsx (authentication check)
  • All action files that call getCookie()
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/api/google/auth/route.ts at lines 16-20, the authentication cookie name
"leadlly.in_admin_token" is hardcoded. This same value is hardcoded in multiple other
files (verify route, logout route, cookie_actions.ts), creating a maintenance and
security risk if they get out of sync. Create a centralized auth configuration file at
src/config/auth.ts that exports AUTH_CONFIG with COOKIE_NAME and COOKIE_MAX_AGE
constants. Update line 16 to use AUTH_CONFIG.COOKIE_NAME instead of the hardcoded
string. Also consider changing the cookie expiration from year 9999 to a more
reasonable value like 30 days using maxAge instead of expires. Then update all other
files that reference this cookie name to use the same centralized constant for
consistency and security.

📍 This suggestion applies to lines 16-20

import { getUser } from "./actions/user_actions";

export async function proxy(request: NextRequest) {
const token = request.cookies.get("leadlly.in_admin_token")?.value;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Security Issue - Hardcoded Cookie Name & Potential Authentication Bypass: This line hardcodes the authentication cookie name as "leadlly.in_admin_token", which is part of a systematic cookie name change across the entire codebase (see Comments #4-#13).
Critical Problems:

  1. Inconsistency Risk: If the actual authentication cookie set by /api/auth/login/route.ts or /api/google/auth/route.ts is named "token" (as the original code suggested), this proxy will fail to retrieve the authentication token, causing:
  • All authenticated users to be redirected to /login (line 17)
  • Complete application lockout for legitimate users
  • Authentication bypass if the check fails open
  1. Lack of Centralization: The cookie name is hardcoded in at least 13 different locations across the codebase. If these values get out of sync during maintenance or security incident response, authentication will break unpredictably.
  2. Security Incident Response: If a security incident requires changing the cookie name (e.g., after credential leak), you must manually update 13+ files, increasing the risk of missing one and creating authentication vulnerabilities.
    This is particularly critical because:
  • This proxy function is the authentication gateway for the entire application
  • It's called on EVERY request via the middleware matcher (line 37)
  • A failure here affects ALL routes, not just individual API calls
    Immediate verification required:
  1. Check what cookie name is actually set in /api/auth/login/route.ts and /api/google/auth/route.ts
  2. If it's "token", this is a breaking change causing complete application lockout
  3. If it's "leadlly.in_admin_token", verify ALL authentication flows use this name consistently

Confidence: 5/5

Recommended Action

Create a centralized auth configuration (e.g., src/config/auth.ts):

export const AUTH_CONFIG = {
COOKIE_NAME: process.env.AUTH_COOKIE_NAME || "leadlly.in_admin_token",
COOKIE_MAX_AGE: 30 * 24 * 60 * 60, // 30 days in seconds
} as const;

Then update this file:

import { AUTH_CONFIG } from "./config/auth";
export async function proxy(request: NextRequest) {
const token = request.cookies.get(AUTH_CONFIG.COOKIE_NAME)?.value;
// ... rest of the code
}

Benefits:

  • Single source of truth for cookie name
  • Environment variable support for different deployments
  • Easy to change cookie name during security incidents
  • Prevents synchronization issues across 13+ files
    Critical: Update ALL files that reference this cookie name to use the same centralized constant:
  • src/actions/cookie_actions.ts (default parameter)
  • src/app/(protected)/layout.tsx (line 10)
  • src/app/api/auth/logout/route.ts (line 7)
  • src/app/api/auth/verify/route.ts (line 15)
  • src/app/api/google/auth/route.ts (line 16)
  • All action files that call getCookie()
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/proxy.ts at line 7, the authentication cookie name "leadlly.in_admin_token" is
hardcoded. This is part of a systematic cookie name change across 13+ files in the
codebase, creating a critical security risk. If the actual authentication cookie name
set by the login routes is "token" (as original code suggested), this will cause
complete application lockout - all authenticated users will be redirected to /login.
Create a centralized auth configuration file at src/config/auth.ts that exports
AUTH_CONFIG with COOKIE_NAME constant (using environment variable with fallback).
Update line 7 to use AUTH_CONFIG.COOKIE_NAME. Then update ALL other files that
reference this cookie name (cookie_actions.ts, protected layout, logout route, verify
route, google auth route, and all action files) to use the same centralized constant.
Verify immediately what cookie name is actually set in the login routes to ensure
consistency.

headers: {
"Content-Type": "application/json",
Cookie: `token=${token}`,
isAdmin: "true",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Security Issue - Client-Controlled Authorization Header: Adding isAdmin: "true" as a request header is a severe security vulnerability if the backend API trusts this header for authorization decisions. This header is set on the client side and can be easily manipulated by attackers.
Attack Scenario:

  1. Attacker intercepts or modifies the request
  2. Adds or keeps isAdmin: "true" header
  3. If backend trusts this header, attacker gains admin privileges
  4. Can create institutes with elevated permissions or bypass authorization checks
    Impact: If the backend /api/institute endpoint uses this header to determine admin status without proper server-side authentication/authorization, this creates a complete authorization bypass vulnerability allowing any user to perform admin-level operations.

Confidence: 5/5

Recommended Action

Immediate verification required:

  1. Review the backend /api/institute endpoint implementation
  2. Ensure it performs proper server-side authorization using the authentication token
  3. Verify it does NOT trust the isAdmin header for authorization decisions
    If the header is required:
  • The backend MUST validate admin status from the JWT token or session, NOT from this header
  • The header should only be used as a hint or for routing, never for authorization
  • Consider removing this header entirely if the backend can determine admin status from the token
    Best practice:
  • Authorization decisions must ALWAYS be made server-side based on authenticated session/token
  • Never trust client-provided headers for security decisions
  • The authentication token (line 25) should contain all necessary authorization information
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/institute_actions.ts at line 26, a client-controlled header "isAdmin: true"
is being sent to the backend API. This is a critical security vulnerability if the backend
trusts this header for authorization decisions. Review the backend /api/institute endpoint
to ensure it performs proper server-side authorization using the authentication token and
does NOT trust the isAdmin header for authorization. If the backend correctly validates
admin status from the token, document why this header is needed. If the backend relies on
this header for authorization, this is a critical vulnerability that must be fixed by
implementing proper server-side authorization checks based on the authenticated token.

headers: {
"Content-Type": "application/json",
Cookie: `token=${token}`,
isAdmin: "true",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Security Issue - Client-Controlled Authorization Header: Adding isAdmin: "true" as a request header is a severe security vulnerability if the backend API trusts this header for authorization decisions. This header is set on the client side and can be easily manipulated by attackers.
Attack Scenario:

  1. Attacker intercepts or modifies the request using browser DevTools or a proxy
  2. Adds or keeps isAdmin: "true" header in any request
  3. If backend /api/admin/get endpoint trusts this header, attacker gains admin privileges
  4. Can retrieve admin user data or bypass authorization checks
    Impact: If the backend endpoint uses this header to determine admin status without proper server-side authentication/authorization validation, this creates a complete authorization bypass vulnerability allowing any authenticated user to access admin-only data.
    Related to Comment #15: This is the same pattern identified in institute_actions.ts where isAdmin: "true" header was added. This appears to be a systematic issue across multiple action files.

Confidence: 5/5

Recommended Action

Immediate verification required:

  1. Review the backend /api/admin/get endpoint implementation
  2. Ensure it performs proper server-side authorization using the authentication token (JWT validation)
  3. Verify it does NOT trust the isAdmin header for authorization decisions
    If the header is required:
  • The backend MUST validate admin status from the JWT token payload or session, NOT from this header
  • The header should only be used as a routing hint or API versioning, never for authorization
  • Consider removing this header entirely if the backend can determine admin status from the token
    Best practice:
  • Authorization decisions must ALWAYS be made server-side based on authenticated session/token claims
  • Never trust client-provided headers for security-critical decisions
  • The authentication token (line 154) should contain all necessary authorization information (roles, permissions)
  • Backend should decode and validate the JWT token to extract admin status
    If keeping the header:
    Document clearly in code comments that this header is NOT used for authorization and is only for routing/logging purposes.
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/user_actions.ts at line 155, a client-controlled header "isAdmin: true"
is being sent to the backend API endpoint /api/admin/get. This is a critical security
vulnerability if the backend trusts this header for authorization decisions. Review the
backend /api/admin/get endpoint to ensure it performs proper server-side authorization
by validating the JWT token and extracting admin status from the token payload, NOT
from the isAdmin header. If the backend correctly validates admin status from the token
claims, document in a code comment why this header is needed (e.g., for routing or
logging only). If the backend relies on this header for authorization, this is a
critical vulnerability that must be fixed immediately by implementing proper server-side
JWT validation and role checking based on the authenticated token.

"/api/google/auth",
{
access_token: credentialResponse.access_token,
isAdmin: true,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Security Issue - Client-Controlled Authorization Header: Adding isAdmin: true as a request body parameter is a severe security vulnerability if the backend API trusts this parameter for authorization decisions. This value is set on the client side and can be easily manipulated by attackers.
Attack Scenario:

  1. Attacker intercepts or modifies the request using browser DevTools or a proxy
  2. Sets isAdmin: true in the request body
  3. If backend /api/google/auth endpoint trusts this parameter, attacker gains admin privileges during Google OAuth login
  4. Can register as an admin user or elevate privileges during authentication
    Impact: If the backend endpoint uses this parameter to determine admin status without proper server-side validation, this creates a complete authorization bypass vulnerability allowing any user with a Google account to register as an admin.
    Related to Comments #15 and #16: This is the same pattern identified in institute_actions.ts and user_actions.ts where isAdmin: "true" header was added. This appears to be a systematic issue across multiple files in this PR.

Confidence: 5/5

Recommended Action

Immediate verification required:

  1. Review the backend /api/google/auth/route.ts endpoint implementation (modified in this PR - see Comment feat(api): add teacher and student management endpoints with filtering, pagination and type safety #13)
  2. Ensure it performs proper server-side authorization and does NOT trust the isAdmin parameter for authorization decisions
  3. Verify that admin status is determined by server-side logic (e.g., checking email domain, pre-approved admin list, or existing database records)
    If the parameter is required:
  • The backend MUST validate admin status through server-side logic, NOT from this client-provided parameter
  • The parameter should only be used as a hint for UI/routing purposes, never for authorization
  • Consider removing this parameter entirely if the backend can determine admin status independently
    Best practice:
  • Authorization decisions must ALWAYS be made server-side based on trusted data sources
  • Never trust client-provided parameters for security-critical decisions
  • Admin status should be determined by server-side validation (e.g., email whitelist, database lookup, or organizational domain verification)
    If keeping the parameter:
    Document clearly in code comments that this parameter is NOT used for authorization and is only for client-side routing/UI purposes. The backend must independently verify admin status.
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/(auth)/_components/GoogleLoginButton.tsx at line 29, a client-controlled
parameter "isAdmin: true" is being sent to the backend API endpoint /api/google/auth.
This is a critical security vulnerability if the backend trusts this parameter for
authorization decisions during Google OAuth authentication. Review the backend
/api/google/auth/route.ts endpoint to ensure it performs proper server-side
authorization and determines admin status through trusted server-side logic (such as
email domain validation, pre-approved admin list, or database lookup), NOT from the
isAdmin parameter sent by the client. If the backend correctly validates admin status
independently, document in a code comment why this parameter is needed (e.g., for
client-side routing only). If the backend relies on this parameter for authorization,
this is a critical vulnerability that must be fixed immediately by implementing proper
server-side admin validation logic.


res.cookies.set('token', '', {
await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue - Backend API URL Exposure: Using NEXT_PUBLIC_ADMIN_API_BASE_URL exposes the internal admin API base URL to all client-side code. Environment variables prefixed with NEXT_PUBLIC_* are embedded in the client-side JavaScript bundle and are visible to anyone inspecting the browser DevTools, page source, or network requests.
Security Risks:

  • Attackers can discover the backend admin API endpoint structure
  • If the backend API is meant to be internal/private, this exposure defeats that security boundary
  • Attackers can attempt direct attacks against the exposed backend API endpoints
  • The full API base URL becomes public knowledge, making targeted attacks easier
    Impact: This is particularly concerning for an admin API, as it exposes administrative endpoints that should typically remain hidden from public view.

Confidence: 5/5

Recommended Action

Option 1: Use server-side environment variable (Recommended)
Since this is a Next.js API route (server-side), use a server-only environment variable:

await fetch(
`${process.env.ADMIN_API_BASE_URL}/api/auth/logout`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
}
);

Then in your .env file:

ADMIN_API_BASE_URL=https://internal-admin-api.example.com

Option 2: Use relative URL if backend is same domain
If the backend API is hosted on the same domain, use a relative URL:

await fetch("/api/auth/logout", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
});

Additional Recommendations:

  • Add error handling for the fetch call to log backend logout failures
  • Consider if this backend call is necessary - the cookie clearing on line 15 might be sufficient
  • If keeping the backend call, document why it's needed (e.g., server-side session invalidation)
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/api/auth/logout/route.ts at line 6, the code uses NEXT_PUBLIC_ADMIN_API_BASE_URL
which exposes the internal admin API base URL to all client-side code. Since this is a
Next.js API route (server-side), change it to use a server-only environment variable
instead. Replace process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL with
process.env.ADMIN_API_BASE_URL (without the NEXT_PUBLIC_ prefix). Update your .env file
to define ADMIN_API_BASE_URL with the backend API URL. This prevents exposing internal
API endpoints to the client-side JavaScript bundle. Also consider adding error handling
for the fetch call to log any backend logout failures.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
src/actions/batch_actions.ts (2)

59-67: ⚠️ Potential issue | 🟡 Minor

Same copy-paste error in getInstituteBatch.

Error messages should reference batch retrieval, not "daily quiz answers".

🐛 Proposed fix
  } catch (error) {
    if (error instanceof Error) {
-      throw new Error(`Error in saving daily quiz answers: ${error.message}`);
+      throw new Error(`Error in fetching institute batches: ${error.message}`);
    } else {
      throw new Error(
-        "An unknown error occurred while saving daily quiz answers!",
+        "An unknown error occurred while fetching institute batches!",
      );
    }
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/batch_actions.ts` around lines 59 - 67, The catch block inside
getInstituteBatch currently throws errors mentioning "saving daily quiz answers"
due to copy-paste; update the thrown messages in the catch (the branches
checking error instanceof Error and the else branch) to reference batch
retrieval (e.g., "Error retrieving institute batch" or "An unknown error
occurred while retrieving institute batch") and include the original
error.message in the instanceof Error branch so getInstituteBatch's logs
correctly describe the failure (locate the catch block in getInstituteBatch
where it does throw new Error(...)).

31-39: ⚠️ Potential issue | 🟡 Minor

Error messages reference wrong functionality.

The error messages mention "daily quiz answers" but this function handles batch creation. This appears to be a copy-paste error that could cause confusion during debugging.

🐛 Proposed fix
  } catch (error) {
    if (error instanceof Error) {
-      throw new Error(`Error in saving daily quiz answers: ${error.message}`);
+      throw new Error(`Error in creating batch: ${error.message}`);
    } else {
      throw new Error(
-        "An unknown error occurred while saving daily quiz answers!",
+        "An unknown error occurred while creating batch!",
      );
    }
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/batch_actions.ts` around lines 31 - 39, The catch block in
src/actions/batch_actions.ts currently throws errors mentioning "daily quiz
answers" (the string "Error in saving daily quiz answers") which is a copy-paste
mistake; update both thrown messages to correctly reference batch creation
(e.g., "Error creating batch(s)" for Error instances and "An unknown error
occurred while creating batches" for the unknown branch) so logs match the
function's purpose—locate the catch that throws new Error with "Error in saving
daily quiz answers" and replace those messages accordingly.
src/actions/mentor_actions.ts (2)

82-98: ⚠️ Potential issue | 🟠 Major

Missing authentication token in API request.

getInstituteStudents doesn't include the authentication token in the request headers, unlike addTeacherToInstitute. If this endpoint requires authentication, requests will fail.

🔒 Proposed fix to add authentication
 export async function getInstituteStudents(instituteId: string) {
   try {
+    const token = await getCookie();
+
     // Validate input
     if (!instituteId) {
       throw new Error("Institute ID is required");
     }

     // Make API call to backend
     const response = await fetch(
       `${process.env.NEXT_PUBLIC_API_URL}/api/institutes/${instituteId}/students`,
       {
         method: "GET",
         headers: {
           "Content-Type": "application/json",
+          Cookie: `token=${token}`,
         },
       }
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/mentor_actions.ts` around lines 82 - 98, getInstituteStudents is
missing the authentication token in its fetch headers; mirror the approach used
in addTeacherToInstitute by retrieving the same auth token (use the existing
auth helper or the same storage mechanism used by addTeacherToInstitute), add an
Authorization header like "Authorization: Bearer <token>" to the request options
in getInstituteStudents, and validate the token exists (throw or handle error if
absent) while preserving the existing Content-Type header.

7-16: ⚠️ Potential issue | 🟡 Minor

JSDoc comment doesn't match function name.

The JSDoc describes "Add students to an institute" but the function is named addTeacherToInstitute. Either update the documentation to reflect "teachers" or rename the function if it's actually for students.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/mentor_actions.ts` around lines 7 - 16, The JSDoc for
addTeacherToInstitute is incorrect (it says "Add students to an institute");
either update the comment to describe adding teachers or rename the function to
reflect student behavior—inspect the implementation of addTeacherToInstitute to
determine its purpose, then (a) if it manages teachers, change the JSDoc summary
and param descriptions to mention teachers and teacher emails, or (b) if it
actually adds students, rename the function to addStudentToInstitute (and update
any references) and adjust the JSDoc accordingly; ensure the function name,
JSDoc summary, and parameter descriptions (emails, instituteId) are consistent.
src/app/(protected)/(root)/institute/[instituteId]/batches/[batchId]/students/page.tsx (1)

206-248: ⚠️ Potential issue | 🟠 Major

Invalid Button variant "outline-solid" — use existing variant instead.

The buttonVariants only define: default, outline, secondary, ghost, destructive, link. The "outline-solid" variant does not exist and will cause type/styling issues.

Fix — replace with valid variant
               <Button
-                variant={filter === "All" ? "default" : "outline-solid"}
+                variant={filter === "All" ? "default" : "outline"}

Apply to all four filter buttons (lines 206, 217, 228, 239).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(protected)/(root)/institute/[instituteId]/batches/[batchId]/students/page.tsx
around lines 206 - 248, The Button components that render the filters use a
non-existent variant "outline-solid"; update each of the filter Buttons (the
ones comparing filter === "All"/"Excellent"/"Optimal"/"Insufficient") to use a
valid variant from buttonVariants (e.g., replace "outline-solid" with "outline")
so the variant prop on those Buttons matches the defined variants and styling
works; keep the conditional logic (variant={filter === "X" ? "default" :
"outline"}) and the onClick handlers that call handleFilter("X") unchanged.
src/app/(protected)/(root)/(dashboard)/page.tsx (1)

60-82: ⚠️ Potential issue | 🟠 Major

Don’t ship the dashboard with its body commented out.

Lines 60-82 remove the institute, students, and teachers overview cards from the rendered page, so this route now lands as a title plus an empty content area. If the replacement UI is not ready, keep the existing section or gate the new behavior behind a flag.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/(dashboard)/page.tsx around lines 60 - 82, The
dashboard page currently has its main content commented out, removing
InstituteOverview, StudentsOverview, and TeachersOverview and leaving only the
title; restore those components (InstituteOverview, StudentsOverview,
TeachersOverview) so they consume dashboardData (dashboardData.institute,
dashboardData.students, dashboardData.teachers) and render normally, or if the
new UI isn't ready wrap the new layout behind a feature flag and render the
existing components when the flag is disabled; ensure props match the existing
prop names (instituteCode, address, contact, email, totalStudents,
activeCourses, averageAttendance, performanceIndex, totalTeachers, departments,
activeClasses, satisfactionRate) and remove the comment block so the components
appear on the page.
src/app/(auth)/_components/GoogleLoginButton.tsx (1)

21-43: ⚠️ Potential issue | 🟠 Major

Remove auth payload logging from the client flow.

Line 23 logs the Google OAuth response, which includes the access token, and Line 43 logs the backend auth response. Both are sensitive for a production login path and should not be written to the browser console.

🔒 Suggested fix
     onSuccess: async (credentialResponse) => {
       setIsLoading(true);
-      console.log("Google login successful:", credentialResponse);
       try {
         const res = await axios.post(
           "/api/google/auth",
@@
         toast.success("Login success", {
           description: res.data.message,
         });
-
-        console.log(res.data);
 
         if (res.status === 201) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(auth)/_components/GoogleLoginButton.tsx around lines 21 - 43, The
client flow currently logs sensitive auth data: remove the console.log("Google
login successful:", credentialResponse) inside the onSuccess handler and the
console.log(res.data) after the axios.post call; instead, if you need a debug
hint use a non-sensitive message (e.g., "Google login succeeded" or rely on
toast.success) and perform any detailed logging server-side. Locate these logs
in the onSuccess callback (references: onSuccess, credentialResponse,
axios.post, res.data, toast.success) and delete or replace the console
statements so access_token and backend auth payloads are never written to the
browser console.
src/app/StoreProvider.tsx (1)

17-22: ⚠️ Potential issue | 🟠 Major

Keep the Redux user state synced when the user prop changes.

This only dispatches userData(user) on the first render. If the server passes a different admin after hydration or a later navigation, the store keeps the stale value.

🧭 Suggested fix
-import { useRef } from "react";
+import { useEffect, useRef } from "react";
@@
   const storeRef = useRef<AppStore | null>(null);
   if (storeRef.current === null) {
-    // Create the store instance the first time this renders
     storeRef.current = makeStore();
-    storeRef.current.dispatch(userData(user));
   }
+
+  useEffect(() => {
+    storeRef.current?.dispatch(userData(user));
+  }, [user]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/StoreProvider.tsx` around lines 17 - 22, The store is only seeded
once so changes to the incoming user prop aren't propagated; after creating
storeRef.current via makeStore() and dispatching userData(user) on initial
render, also add an effect that watches the user prop and dispatches
storeRef.current.dispatch(userData(user)) whenever user changes (guarding that
storeRef.current is non-null). This keeps the Redux user state in sync with the
user prop while preserving the initial creation logic that uses makeStore() and
userData.
🟠 Major comments (20)
src/components/ui/dropdown-menu.tsx-50-50 (1)

50-50: ⚠️ Potential issue | 🟠 Major

Switch these Radix CSS-variable classes to Tailwind v4 syntax.

origin-[--radix-dropdown-menu-content-transform-origin] and max-h-[var(--radix-dropdown-menu-content-available-height)] are v3-style arbitrary values. In Tailwind v4, these utilities need the parenthesized CSS-variable form, otherwise the max-height/origin styles won't be generated.

♻️ Proposed fix
-      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
+      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin)",
-        "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
-        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
+        "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
+        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin)",

Also applies to: 68-69

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/dropdown-menu.tsx` at line 50, In the DropdownMenu content
class string (in src/components/ui/dropdown-menu.tsx, e.g., the
DropdownMenuContent/DropdownMenu component), replace v3-style arbitrary
utilities using raw CSS variables with Tailwind v4 parenthesized form: change
origin-[--radix-dropdown-menu-content-transform-origin] to
origin-[var(--radix-dropdown-menu-content-transform-origin)] and any
max-h-[var(--radix-dropdown-menu-content-available-height)] usages to
max-h-[var(--radix-dropdown-menu-content-available-height)] (update all
occurrences, including the other instances referenced in the comment).
src/app/api/google/auth/route.ts-18-23 (1)

18-23: ⚠️ Potential issue | 🟠 Major

Add secure flag to cookie for production.

The cookie is missing the secure flag, which means it could be transmitted over insecure HTTP connections. For authentication tokens, this should be true in production environments.

🔒 Proposed fix
    res.cookies.set("leadlly.in_admin_token", token, {
      httpOnly: true,
+     secure: process.env.NODE_ENV === "production",
      path: "/",
      sameSite: "strict",
      expires: new Date("9999-12-31T23:59:59Z"),
    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/google/auth/route.ts` around lines 18 - 23, The cookie set in
res.cookies.set("leadlly.in_admin_token", ...) lacks the secure flag; update the
res.cookies.set call in src/app/api/google/auth/route.ts to include secure: true
when running in production (e.g., process.env.NODE_ENV === "production" or an
isProduction env var) so the token is only sent over HTTPS in prod, keeping the
existing httpOnly, path, sameSite, and expires settings intact.
src/app/api/google/auth/route.ts-10-10 (1)

10-10: ⚠️ Potential issue | 🟠 Major

Remove debug logging of request body.

Logging the full request body may expose sensitive authentication data (tokens, user credentials) in production logs. This is a compliance/privacy risk.

🐛 Proposed fix
-    console.log(body, "here is the body");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/google/auth/route.ts` at line 10, Remove the debug console.log
that prints the request body to avoid leaking sensitive auth data: locate the
console.log(body, "here is the body") call in src/app/api/google/auth/route.ts
(inside the route handler that processes the auth request), delete that
statement, and if you need observability replace it with a non-sensitive log
(e.g., log only safe metadata like request id or status) or use a
conditional/debug-only logger that never runs in production.
src/proxy.ts-26-32 (1)

26-32: ⚠️ Potential issue | 🟠 Major

Missing null check before accessing user.admin.institutes[0].

If getUser() fails or returns unexpected data, accessing user.admin.institutes[0] will throw. Add defensive checks.

🛡️ Proposed fix
   if (token && isPublicPath) {
-    const user = await getUser();
-
-    return NextResponse.redirect(
-      new URL(`/institute/${user.admin.institutes[0].toString()}`, request.url)
-    );
+    try {
+      const user = await getUser();
+      if (user?.admin?.institutes?.length > 0) {
+        return NextResponse.redirect(
+          new URL(`/institute/${user.admin.institutes[0].toString()}`, request.url)
+        );
+      }
+      return NextResponse.redirect(new URL("/create-institute", request.url));
+    } catch {
+      // If user fetch fails, allow access to public path
+      return NextResponse.next();
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/proxy.ts` around lines 26 - 32, The code redirects to
`/institute/${user.admin.institutes[0]}` without verifying getUser() returned a
valid object and that admin/institutes exist and contain an entry; update the
block handling token && isPublicPath to validate the returned value from
getUser() (check user is non-null, user.admin exists, user.admin.institutes is
an array and has length > 0 and that institutes[0] is defined) before calling
NextResponse.redirect, and if any check fails either skip the redirect or choose
a safe fallback (e.g., redirect to a default page or return
NextResponse.next()); reference the token/isPublicPath condition, getUser(), and
the NextResponse.redirect call when making the change.
src/actions/mentor_actions.ts-40-50 (1)

40-50: ⚠️ Potential issue | 🟠 Major

Confirm API base URL inconsistency and check for missing admin header.

The inconsistency is confirmed: addTeacherToInstitute uses NEXT_PUBLIC_ADMIN_API_BASE_URL while getInstituteStudents uses NEXT_PUBLIC_API_URL. The same pattern exists in student_action.ts, suggesting intentional separation of admin vs. public endpoints.

However, compare the add operations across both files: student_action.ts includes isAdmin: "true" header in its add request, but mentor_actions.ts does not. Verify whether this header is required and if mentor_actions.ts is missing it unintentionally.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/mentor_actions.ts` around lines 40 - 50, The POST in
mentor_actions.ts uses NEXT_PUBLIC_ADMIN_API_BASE_URL for /api/student/add but
omits the isAdmin header that student_action.ts includes; verify whether this
endpoint expects the admin flag and either (a) add headers: { "Content-Type":
"application/json", "Cookie": `token=${token}`, "isAdmin": "true" } to the fetch
call shown, or (b) if the endpoint should be the public API, change to
NEXT_PUBLIC_API_URL to match getInstituteStudents — update the fetch invocation
that constructs the URL and headers (using
process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL, the POST to
/api/student/add/${instituteId}, and the Cookie token) accordingly so the admin
header is consistent with student_action.ts.
src/app/(protected)/create-institute/page.tsx-15-16 (1)

15-16: ⚠️ Potential issue | 🟠 Major

Don’t vertically center a form page.

Line 15 uses items-center on a min-h-screen wrapper. Once CreateInstituteForm grows beyond the viewport—mobile, validation errors, browser zoom—the top of the card can become unreachable.

💡 Proposed fix
-    <div className="container mx-auto min-h-screen px-4 py-8 max-w-2xl flex items-center justify-center w-full">
+    <div className="container mx-auto min-h-screen px-4 py-8 max-w-2xl flex items-start justify-center w-full">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/create-institute/page.tsx around lines 15 - 16, The
wrapper div currently uses min-h-screen with items-center which vertically
centers the Card and can make the top unreachable when the form grows; update
the container's layout by removing or replacing items-center (e.g., use
items-start or omit vertical alignment) and keep sufficient top/bottom padding
(px-4 py-8) so the Card and the CreateInstituteForm can scroll normally; locate
the outer div with className="container mx-auto min-h-screen px-4 py-8 max-w-2xl
flex items-center justify-center w-full" and the Card component wrapping
CreateInstituteForm and change the flex alignment accordingly.
src/app/provider.tsx-12-14 (1)

12-14: ⚠️ Potential issue | 🟠 Major

Handle getUser() failures before rendering the provider.

Line 12 is unguarded. If the lookup throws during SSR, the whole tree fails instead of degrading to user={null}.

💡 Proposed fix
-  const userData = await getUser();
+  const userData = await getUser().catch(() => null);
 
-  return <StoreProvider user={userData?.admin}>{children}</StoreProvider>;
+  return <StoreProvider user={userData?.admin ?? null}>{children}</StoreProvider>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/provider.tsx` around lines 12 - 14, The getUser() call can throw
during SSR causing the whole render to fail; wrap the call to getUser() in a
try/catch inside the async provider component (the place that assigns userData)
and on error (or if it returns undefined) set userData to null before rendering;
then pass user={userData?.admin ?? null} into StoreProvider (the JSX that
returns <StoreProvider user={...}>{children}</StoreProvider>) so failures
degrade to no user instead of crashing.
src/app/api/auth/verify/route.ts-15-20 (1)

15-20: ⚠️ Potential issue | 🟠 Major

Add secure flag to the admin auth cookie to prevent transmission over HTTP.

Lines 15-20 set the admin token cookie without the secure flag, which allows browsers to transmit it over unencrypted HTTP connections outside of development environments. This weakens the security boundary for a sensitive authentication token.

💡 Proposed fix
     res.cookies.set("leadlly.in_admin_token", token, {
       httpOnly: true,
+      secure: process.env.NODE_ENV === "production",
       path: "/",
       sameSite: "strict",
       expires: new Date("9999-12-31T23:59:59Z"),
     });

Note: This same issue exists in src/app/api/auth/login/route.ts, src/app/api/auth/logout/route.ts, and src/app/api/google/auth/route.ts—apply the same fix to all admin token cookie configurations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/auth/verify/route.ts` around lines 15 - 20, The admin auth cookie
set via res.cookies.set("leadlly.in_admin_token", token, { ... }) is missing the
secure flag; update the cookie options in the res.cookies.set calls (the ones
that set "leadlly.in_admin_token" in verify/login/logout/google auth routes) to
include secure: true in production (e.g., secure: process.env.NODE_ENV !==
'development' or secure: true behind your TLS-aware environment) so the cookie
is sent only over HTTPS, keeping all other existing options (httpOnly, path,
sameSite, expires) intact.
src/app/(protected)/(root)/institute/[instituteId]/_components/StudentsOverview.tsx-10-10 (1)

10-10: ⚠️ Potential issue | 🟠 Major

Do not make instituteId optional if the link depends on it.

Line 111 will render /institute/undefined/batches whenever this prop is missing, so the CTA silently becomes a broken navigation target. Make instituteId required or hide/disable the link until it exists.

🧭 Suggested fix
 interface StudentsOverviewProps {
   totalStudents: number;
   activeCourses: number;
   averageAttendance: number;
   performanceIndex: number;
-  instituteId?: string;
+  instituteId: string;
 }

Also applies to: 111-111

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(protected)/(root)/institute/[instituteId]/_components/StudentsOverview.tsx
at line 10, The StudentsOverview component currently declares instituteId?:
string which allows rendering a link to `/institute/undefined/batches`; change
the prop to required (in the component props/type: instituteId: string) or,
alternatively, guard the link rendering in StudentsOverview (where the CTA is
created—the anchor/Link that builds `/institute/${instituteId}/batches`) to only
render or enable the link when instituteId is a non-empty string, e.g.,
hide/disable the CTA and provide fallback UI until instituteId exists.
src/app/api/auth/login/route.ts-16-21 (1)

16-21: ⚠️ Potential issue | 🟠 Major

Harden the auth cookie before shipping.

Lines 16-20 create a bearer-token cookie that is effectively permanent and not marked secure. That makes stale sessions linger indefinitely and weakens transport guarantees outside localhost. Prefer a session cookie or derive expiry from the token TTL, and set secure in production.

🔐 Suggested fix
     res.cookies.set("leadlly.in_admin_token", token, {
       httpOnly: true,
+      secure: process.env.NODE_ENV === "production",
       path: "/",
       sameSite: "strict",
-      expires: new Date("9999-12-31T23:59:59Z"),
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/auth/login/route.ts` around lines 16 - 21, The auth cookie set by
res.cookies.set("leadlly.in_admin_token", token, {...}) is too persistent and
not marked secure; update the login route to (1) avoid a permanent expires —
either make it a session cookie by omitting expires or derive expires/maxAge
from the token's TTL/JWT exp (use the token variable or decode its exp) and (2)
set secure: process.env.NODE_ENV === "production" (or equivalent env flag) so
the cookie is only sent over TLS in prod; keep httpOnly, path, sameSite as-is
and prefer using maxAge when deriving TTL.
src/app/(protected)/(root)/layout.tsx-15-20 (1)

15-20: ⚠️ Potential issue | 🟠 Major

Add staleTime to prevent immediate refetch on hydration.

Lines 17-20 prefetch without setting staleTime. React Query defaults to staleTime: 0, so this data becomes stale immediately. When HydrationBoundary restores it on the client, the useSuspenseQuery call in main-header.tsx will see stale data and refetch, creating the double-fetch you're trying to avoid. Add staleTime: 60 * 1000 (or match your QueryProvider defaults) to the prefetch options.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/layout.tsx around lines 15 - 20, The
prefetchQuery call using QueryClient (queryKey ["all_institutes"] and queryFn
getAllUserInstitutes) doesn't set staleTime so the data is immediately
considered stale and triggers a client refetch after hydration; update the
prefetchQuery options to include staleTime: 60 * 1000 (or your QueryProvider
default) so the prefetched data remains fresh across hydration and prevents the
double-fetch in useSuspenseQuery in main-header.tsx.
src/helpers/types/index.ts-588-623 (1)

588-623: ⚠️ Potential issue | 🟠 Major

Do not include credential fields on a client-shared admin type.

IAdmin now contains password and salt, and this type is already flowing into client-side provider/store code in this PR. Keeping secret fields on the shared shape makes accidental serialization to the browser much more likely.

🔐 Suggested fix
 export interface IAdmin {
   _id: string;
   firstname: string;
   lastname: string;
   email: string;
   role: "admin" | "super_admin";
   phone: string;
-  password: string;
-  salt: string;
   avatar: {
     public_id: string;
     url: string;
   };

If server-only code needs those fields, split the model into a public client DTO and a separate server-only type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/helpers/types/index.ts` around lines 588 - 623, The IAdmin interface
exposes sensitive credential fields (password, salt) that are leaking into
client-side code; remove password and salt from the shared IAdmin type and
create a separate server-only interface (e.g., IAdminPrivate or IAdminServer)
that extends the public shape and includes password and salt for server use
only, then update client-side consumers (providers/stores) to depend on the
sanitized public DTO (e.g., IAdminPublic or the updated IAdmin) while server
code (auth, models) imports the server-only interface to access credentials.
src/app/(protected)/(root)/institute/[instituteId]/_components/add-students-dialog.tsx-28-30 (1)

28-30: ⚠️ Potential issue | 🟠 Major

Validate each comma-separated email in the schema.

This currently accepts any non-empty string, so values like foo, bar pass client-side and fail later. Validate the split list up front so the form error is immediate and specific.

✅ Suggested fix
+const parseEmails = (value: string) =>
+  value
+    .split(",")
+    .map((email) => email.trim())
+    .filter(Boolean);
+
 const AddStudentsFormSchema = z.object({
-  emails: z.string().min(1, { error: "Enter valid email addresses" }),
+  emails: z
+    .string()
+    .trim()
+    .min(1, { error: "Enter at least one email address" })
+    .refine(
+      (value) =>
+        parseEmails(value).every(
+          (email) => z.email().safeParse(email).success
+        ),
+      { error: "Enter valid comma-separated email addresses" }
+    ),
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(protected)/(root)/institute/[instituteId]/_components/add-students-dialog.tsx
around lines 28 - 30, The emails field currently only checks non-empty string;
update AddStudentsFormSchema to validate each comma-separated address by
transforming the input into an array (split on commas, trim each entry, filter
out empty strings) and then validate that the resulting array has at least one
item and every entry is a valid email (use z's email validator or a regex) with
a clear error message; update any form handlers that expect a string to accept
the transformed array (or derive the array after validation) so client-side
validation rejects inputs like "foo, bar" immediately.
src/app/(protected)/create-institute/_components/create-institute-form.tsx-169-379 (1)

169-379: ⚠️ Potential issue | 🟠 Major

Fix the label/control associations across this form.

Most FieldLabel elements still point to htmlFor="name" while the actual controls don't expose matching ids. That breaks label click/focus behavior and screen-reader association for nearly every field in this section.

♿ Suggested fix pattern
 <Controller
   control={form.control}
   name="email"
   render={({ field, fieldState }) => (
     <Field data-invalid={fieldState.invalid} className="gap-1.5">
-      <FieldLabel htmlFor="name">Email</FieldLabel>
+      <FieldLabel htmlFor="email">Email</FieldLabel>
       <Input
+        id="email"
         {...field}
         placeholder="Enter institute email"
         required
         aria-invalid={fieldState.invalid}
         className="shadow-none"

Apply the same pattern to each field so every htmlFor matches a unique control id.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/create-institute/_components/create-institute-form.tsx
around lines 169 - 379, Many FieldLabel elements use htmlFor="name" but the
corresponding inputs/selects lack matching id attributes, breaking label-control
association; update each Controller render (e.g., ones with name="name",
"email", "contactNumber", "website", "address1", "address2", "city", "state",
"pincode", "country") so the rendered control (Input or Select) has an id that
matches the FieldLabel htmlFor (use a unique id per field, e.g.,
id={`${field.name}`}), and ensure Select uses the same id on its
SelectTrigger/SelectValue as appropriate; also keep FieldLabel htmlFor values
matched to those ids to restore click/focus and screen-reader associations.
src/app/(protected)/(root)/institute/[instituteId]/page.tsx-41-54 (1)

41-54: ⚠️ Potential issue | 🟠 Major

Replace the mock metrics before shipping this page.

These values are rendered for every institute, so the dashboard always shows the same student and teacher counts regardless of the selected institute. Wire the cards to real stats or hide them until the API exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/institute/[instituteId]/page.tsx around lines 41
- 54, The page currently uses a hard-coded dashboardData object
(students/teachers) so every institute shows identical metrics; replace that
static dashboardData with a real data flow by calling an async data function
(e.g., getInstituteDashboardStats(instituteId) or
fetch(`/api/institutes/${instituteId}/stats`)) inside the page component (or a
server-side loader) and map the returned students and teachers fields into the
existing cards, and if the API is not ready, hide the cards or render a
loading/skeleton state when dashboardData is null/undefined; update references
to dashboardData, students, and teachers to use the fetched stats (or a feature
flag) so metrics are institute-specific.
src/actions/institute_actions.ts-32-36 (1)

32-36: ⚠️ Potential issue | 🟠 Major

updateTag("userData") does not invalidate the institute queries.

None of the fetchers in this file use the userData tag. The getActiveInstitute and getAllUserInstitutes functions have no cache tags defined and will not be invalidated by this call. If React Query is used for these queries, the invalidation layers are separate and won't communicate.

Expected: Define cache tags on the fetchers that read institute data, or use a tag name that actually matches the queries being invalidated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/institute_actions.ts` around lines 32 - 36, The call to
updateTag("userData") does not invalidate institute queries because
getActiveInstitute and getAllUserInstitutes don't use that tag; either change
the tag passed to updateTag to the actual tag those fetchers use (e.g.,
"institute" or whatever cache key those fetchers register) or add the
appropriate cache tag definitions to the institute fetchers (getActiveInstitute
and getAllUserInstitutes) so they include the same tag name; update the call
site (updateTag) or the fetchers’ tag registration so the tag names match and
invalidation will trigger correctly.
src/app/(protected)/(root)/_components/main-header.tsx-71-78 (1)

71-78: ⚠️ Potential issue | 🟠 Major

Use onSelect instead of onClick for institute navigation.

DropdownMenuItem selection is Radix's cross-input activation signal—it fires for both pointer and keyboard interactions (Enter/Space on focused items). Using onClick bypasses keyboard activation, leaving users unable to switch institutes via keyboard navigation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/_components/main-header.tsx around lines 71 - 78,
The DropdownMenuItem currently uses an onClick handler for navigation which
misses Radix's cross-input activation (keyboard) and prevents keyboard users
from selecting institutes; replace the onClick prop on DropdownMenuItem with an
onSelect handler that calls router.replace(`/institute/${institute._id}`)
(preserving the same key, className and activeInstitute check) so selection
works for both pointer and keyboard interactions.
src/components/ui/combobox.tsx-74-82 (1)

74-82: ⚠️ Potential issue | 🟠 Major

Render the InputGroupButton through the trigger's render prop instead of nesting.

The ComboboxTrigger renders a <button> by default, so the current code creates a nested button structure (<button><button>), which is invalid HTML and breaks click/focus behavior. Use the render prop to compose the custom button element.

Suggested fix
         {showTrigger && (
-          <InputGroupButton
-            size="icon-xs"
-            variant="ghost"
-            data-slot="input-group-button"
-            className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
-            disabled={disabled}
-          >
-            <ComboboxTrigger />
-          </InputGroupButton>
+          <ComboboxTrigger
+            disabled={disabled}
+            render={
+              <InputGroupButton
+                size="icon-xs"
+                variant="ghost"
+                data-slot="input-group-button"
+                className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
+                disabled={disabled}
+              />
+            }
+          />
         )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/combobox.tsx` around lines 74 - 82, The InputGroupButton is
being nested with ComboboxTrigger which causes a <button><button> invalid
structure; replace the nested ComboboxTrigger by using the trigger component's
render prop so InputGroupButton becomes the rendered trigger element. Locate the
InputGroupButton/ComboboxTrigger block and remove the inner <ComboboxTrigger />;
instead call the trigger's render prop (e.g., ComboboxTrigger render or similar
API) to pass trigger props/handlers into InputGroupButton so it receives the
trigger's aria/onclick/focus props and retains size/variant/disabled/className.
src/app/(protected)/(root)/institute/[instituteId]/page.tsx-26-31 (1)

26-31: ⚠️ Potential issue | 🟠 Major

Wrap the prefetched query in a HydrationBoundary to avoid duplicate fetches.

prefetchQuery() in a server component alone doesn't make the cached data available to client-side useQuery() hooks—they have their own QueryClient instance. The prefetched data is discarded, and InstituteOverview fetches again on the client, doubling your network requests. You must serialize the server cache with dehydrate(queryClient) and pass it through <HydrationBoundary> for the client to reuse it.

Suggested fix
import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query";

const queryClient = new QueryClient();

await queryClient.prefetchQuery({
  queryKey: ["active_institute", instituteId],
  queryFn: () => getActiveInstitute({ instituteId }),
});

return (
  <HydrationBoundary state={dehydrate(queryClient)}>
    {/* existing page JSX */}
  </HydrationBoundary>
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/institute/[instituteId]/page.tsx around lines 26
- 31, The server-side QueryClient created as QueryClient() and used with
queryClient.prefetchQuery(...) isn't being serialized to the client, so wrap
your returned JSX in a HydrationBoundary and pass state={dehydrate(queryClient)}
(import HydrationBoundary and dehydrate from `@tanstack/react-query`) so the
client-side useQuery() in InstituteOverview can reuse the prefetched cache
instead of refetching; ensure you add the imports for HydrationBoundary and
dehydrate and wrap the existing page JSX with <HydrationBoundary
state={dehydrate(queryClient)}>...</HydrationBoundary>.
src/actions/institute_actions.ts-80-95 (1)

80-95: ⚠️ Potential issue | 🟠 Major

Check both res.ok and responseData.success before returning.

fetch does not throw on HTTP 4xx/5xx status codes—it only rejects on transport failures. Currently, if the API returns 401/404/500 or success: false, the functions return the error response as if successful. Since these functions are used as TanStack Query queryFns (in main-header.tsx, layout.tsx, InstituteOverview.tsx), the query enters the success state instead of the error state when no exception is thrown. Additionally, callers like select-institute/page.tsx dereference data.institutes directly without checking data.success, which would cause runtime errors on API failures.

Suggested fix
     const responseData: { success: boolean; institute: IInstitute } =
       await res.json();
+    if (!res.ok || !responseData.success) {
+      throw new Error("Failed to fetch institute");
+    }
     return responseData;
     const responseData: {
       success: boolean;
       institutes: IInstitute[];
       count: number;
     } = await res.json();
+    if (!res.ok || !responseData.success) {
+      throw new Error("Failed to fetch institutes");
+    }
     return responseData;

Also applies to: 109-127

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/institute_actions.ts` around lines 80 - 95, The fetch block that
builds `res` and parses `responseData` must validate both the HTTP status and
the API payload before returning: after `const res = await fetch(...)` check `if
(!res.ok)` and throw an Error (including `res.status` and statusText or parsed
body) so TanStack Query treats it as a failure; after `const responseData =
await res.json()` also check `if (!responseData.success)` and throw an Error
(include `responseData` or a clear message). Apply this change for the institute
fetch shown (use the existing variables `res`, `responseData`, `instituteId`,
`token`) and replicate the same pattern for the other similar fetch in the file
(the block referenced at lines 109-127) so callers never receive a falsy payload
as success.
🟡 Minor comments (12)
src/app/api/auth/logout/route.ts-17-22 (1)

17-22: ⚠️ Potential issue | 🟡 Minor

Add secure flag for production cookie security.

The cookie should include secure: true in production to ensure it's only transmitted over HTTPS, preventing potential session hijacking over insecure connections.

🔒 Proposed fix
     res.cookies.set("leadlly.in_admin_token", "", {
       httpOnly: true,
       path: "/",
       sameSite: "strict",
+      secure: process.env.NODE_ENV === "production",
       expires: new Date(0),
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/auth/logout/route.ts` around lines 17 - 22, The logout cookie
reset call using res.cookies.set("leadlly.in_admin_token", ...) must include the
secure flag in production; update the options object passed to res.cookies.set
(in route.ts) to add secure: process.env.NODE_ENV === "production" (or an
equivalent runtime env check) so the cookie is only sent over HTTPS in
production while remaining usable in dev.
src/components/ui/skeleton.tsx-1-6 (1)

1-6: ⚠️ Potential issue | 🟡 Minor

Add missing React import for type reference.

The component uses React.HTMLAttributes<HTMLDivElement> but doesn't import React. All other UI components in this project follow the pattern of explicitly importing React for type references.

Proposed fix
+import * as React from "react"
+
 import { cn } from "@/lib/utils"
 
 function Skeleton({

Alternatively, import only the type:

+import type { HTMLAttributes } from "react"
+
 import { cn } from "@/lib/utils"
 
 function Skeleton({
   className,
   ...props
-}: React.HTMLAttributes<HTMLDivElement>) {
+}: HTMLAttributes<HTMLDivElement>) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/skeleton.tsx` around lines 1 - 6, The Skeleton component
uses the type React.HTMLAttributes<HTMLDivElement> but never imports React; add
an explicit import for React at the top of the file (e.g., import React from
"react" or import type React from "react" per project conventions) so the type
reference in the Skeleton function signature resolves correctly.
src/app/api/google/auth/route.ts-36-36 (1)

36-36: ⚠️ Potential issue | 🟡 Minor

Handle potentially undefined status.

If error.response is undefined (e.g., network error), error.response?.status will be undefined, and NextResponse will default to 200. Provide an explicit fallback.

🐛 Proposed fix
-      { status: error instanceof AxiosError ? error.response?.status : 500 }
+      { status: error instanceof AxiosError ? (error.response?.status ?? 500) : 500 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/google/auth/route.ts` at line 36, The status value passed to
NextResponse can be undefined when AxiosError.response is undefined (e.g.,
network errors); update the construction that currently uses error instanceof
AxiosError ? error.response?.status : 500 to provide an explicit fallback (for
example use the nullish coalescing fallback) so the status is always a valid
HTTP status (e.g., default to 500); locate the NextResponse creation in
src/app/api/google/auth/route.ts where error handling uses AxiosError and
replace the conditional to guarantee a numeric status.
src/components/ui/tooltip.tsx-22-25 (1)

22-25: ⚠️ Potential issue | 🟡 Minor

Update CSS variable syntax for Tailwind v4.

The origin-[--radix-tooltip-content-transform-origin] uses bracket syntax for CSS variables, which changed in Tailwind v4. CSS variables now require parentheses instead of brackets.

✏️ Proposed fix
       className={cn(
-        "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",
+        "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-tooltip-content-transform-origin)",
         className
       )}

As per the library documentation for Tailwind v4: "CSS Variables - Brackets → Parentheses: OLD: bg-[--brand-color] NEW: bg-(--brand-color). All arbitrary values using CSS variables must use parentheses."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/tooltip.tsx` around lines 22 - 25, The Tailwind v4 syntax
for CSS variables is wrong in the tooltip's className: replace the
arbitrary-bracket usage origin-[--radix-tooltip-content-transform-origin] with
the v4 parenthesis form origin-(--radix-tooltip-content-transform-origin) inside
the cn(...) call in the Tooltip component so the CSS variable is applied
correctly; update the string passed to className (the one containing
"origin-[--radix-tooltip-content-transform-origin]") to use parentheses.
.prettierignore-4-6 (1)

4-6: ⚠️ Potential issue | 🟡 Minor

Fix typos in glob patterns.

Lines 4 and 6 have erroneous spaces that break the ignore patterns:

  • *. env should be *.env
  • .env .* should be .env.*

These patterns won't match the intended files (e.g., .env.local, .env.production).

✏️ Proposed fix
-*. env
+*.env
 .env
-.env .*
+.env.*
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.prettierignore around lines 4 - 6, The glob patterns in .prettierignore
contain typos: replace the incorrect entries "*. env" and ".env .*" with
corrected patterns "*.env" and ".env.*" so they properly match files like
.env.local and .env.production; update the .prettierignore file to remove the
stray spaces in those two lines (the file entries shown in the diff are the
locations to fix).
src/components/ui/field.tsx-194-215 (1)

194-215: ⚠️ Potential issue | 🟡 Minor

Skip rendering the alert when there are no actual error messages.

If errors is defined but every entry lacks message, this branch still returns an empty <ul>, so FieldError renders a blank role="alert". Filter the messages first and return null when none remain.

💡 Proposed fix
   const content = useMemo(() => {
     if (children) {
       return children
     }
 
-    if (!errors) {
+    const messages = errors?.flatMap((error) =>
+      error?.message ? [error.message] : []
+    ) ?? []
+
+    if (messages.length === 0) {
       return null
     }
 
-    if (errors?.length === 1 && errors[0]?.message) {
-      return errors[0].message
+    if (messages.length === 1) {
+      return messages[0]
     }
 
     return (
       <ul className="ml-4 flex list-disc flex-col gap-1">
-        {errors.map(
-          (error, index) =>
-            error?.message && <li key={index}>{error.message}</li>
-        )}
+        {messages.map((message, index) => (
+          <li key={index}>{message}</li>
+        ))}
       </ul>
     )
   }, [children, errors])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/field.tsx` around lines 194 - 215, The current useMemo for
content returns an empty <ul> when errors exists but none have a message,
causing FieldError to render a blank alert; update the useMemo in the content
computation to first derive a filteredMessages = (errors || []).filter(e =>
e?.message) and if filteredMessages.length === 0 return null, otherwise if
filteredMessages.length === 1 return filteredMessages[0].message, else render
the <ul> by mapping filteredMessages instead of errors (keep the existing
children-shortcircuit intact).
src/app/(auth)/login/page.tsx-21-23 (1)

21-23: ⚠️ Potential issue | 🟡 Minor

Add explicit alt text to the logo image.

Line 22 renders an image without alt, which makes the header noisier for screen readers. Use alt="" if this is purely decorative, or a short brand label if it is informative.

♿ Suggested fix
           <Avatar className="mx-auto mb-6 bg-primary/10 size-12">
-            <AvatarImage src="/leadlly.jpeg" className="size-full" />
+            <AvatarImage
+              src="/leadlly.jpeg"
+              alt="Leadlly logo"
+              className="size-full"
+            />
           </Avatar>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(auth)/login/page.tsx around lines 21 - 23, The AvatarImage inside
the login page (component Avatar / AvatarImage in page.tsx) is missing an alt
attribute; update the AvatarImage element to include an explicit alt (use alt=""
if the image is purely decorative or a short brand label like "Leadlly logo" if
it conveys information) so screen readers do not announce noisy or missing-alt
warnings.
src/helpers/schema/createInstituteSchema.ts-10-10 (1)

10-10: ⚠️ Potential issue | 🟡 Minor

contactNumber currently accepts arbitrary 10-character strings.

Line 10 only checks length, so obviously invalid values can still pass while the message says “valid phone number”. Tighten this to the phone formats you actually support before persisting it.

📞 Suggested fix
-  contactNumber: z.string().min(10, "Please enter a valid phone number"),
+  contactNumber: z
+    .string()
+    .trim()
+    .regex(/^\+?[0-9\s-]{10,15}$/, "Please enter a valid phone number"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/helpers/schema/createInstituteSchema.ts` at line 10,
createInstituteSchema's contactNumber currently only checks length and accepts
arbitrary 10-char strings; change the schema for contactNumber in
createInstituteSchema to validate against the supported phone formats (e.g.,
digits-only 10-digit, optional country code/E.164 with leading '+', or your
app's accepted patterns) using z.string().regex(...) or z.string().refine(...)
with a clear error message; update the validation pattern/error text so invalid
formats are rejected before persisting and reference the contactNumber field in
the schema for locating the change.
src/app/(protected)/create-institute/_components/create-institute-form.tsx-482-493 (1)

482-493: ⚠️ Potential issue | 🟡 Minor

Fix the Tailwind class typo on the add-subject icon.

mr-2size-4 is parsed as one invalid class, so you lose both the spacing and the icon sizing here.

🪄 Suggested fix
-<Plus className="mr-2size-4" />
+<Plus className="mr-2 size-4" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/create-institute/_components/create-institute-form.tsx
around lines 482 - 493, The Plus icon's className has a typo "mr-2size-4" which
merges two classes into one invalid token; update the class on the Plus element
inside the Button in create-institute-form.tsx (the Add "… " button within the
CreateInstituteForm component) to separate the spacing and sizing classes (e.g.,
use "mr-2" and a size class such as "size-4" or "h-4 w-4") so the margin and
icon size are applied correctly.
src/app/(protected)/(root)/institute/[instituteId]/page.tsx-90-105 (1)

90-105: ⚠️ Potential issue | 🟡 Minor

Wire up or remove the Edit action.

This renders as a primary CTA, but it has no navigation target or click handler. Right now users hit a dead end.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/institute/[instituteId]/page.tsx around lines 90
- 105, The "Edit" Button in page.tsx is rendered without a click handler or
navigation target (dead CTA); either wire it to the institute edit route or
remove it. Fix by updating the Button usage (the Button element containing the
SVG and "Edit" text) to perform navigation: add an onClick that uses Next.js
router.push(`/institute/${instituteId}/edit`) or replace Button with a Link
component with href to the appropriate edit path, and include an accessible
label (aria-label="Edit institute") and any necessary disabled/loading state
handling so the CTA is actionable and meaningful.
src/components/ui/input-group.tsx-60-77 (1)

60-77: ⚠️ Potential issue | 🟡 Minor

Addon focus delegation only works for plain inputs.

The click handler hard-codes querySelector("input"), so InputGroupTextarea and any future control using data-slot="input-group-control" never receive focus from the addon. Because {...props} comes after onClick, a consumer can also overwrite the built-in focus behavior by accident.

Suggested fix
 function InputGroupAddon({
   className,
   align = "inline-start",
+  onClick,
   ...props
 }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
   return (
     <div
       role="group"
       data-slot="input-group-addon"
       data-align={align}
       className={cn(inputGroupAddonVariants({ align }), className)}
+      {...props}
       onClick={(e) => {
+        onClick?.(e)
+        if (e.defaultPrevented) {
+          return
+        }
         if ((e.target as HTMLElement).closest("button")) {
           return
         }
-        e.currentTarget.parentElement?.querySelector("input")?.focus()
+        e.currentTarget.parentElement
+          ?.querySelector<HTMLElement>('[data-slot="input-group-control"]')
+          ?.focus()
       }}
-      {...props}
     />
   )
 }
src/app/(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx-16-19 (1)

16-19: ⚠️ Potential issue | 🟡 Minor

Use useSuspenseQuery or add a loading state to match the Suspense boundary.

The useQuery hook in TanStack Query v5 does not suspend, so the <Suspense> boundary wrapping this component (in page.tsx, line 110) won't catch its loading state. While the parent pre-fetches the query, if the cache isn't hydrated or the client-side query runs before hydration is complete, the component will render with undefined activeInstitute instead of showing the fallback. Either switch to useSuspenseQuery to integrate with Suspense, or add an explicit loading/error branch within the component.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx
around lines 16 - 19, The component currently calls useQuery({ queryKey:
["active_institute", instituteId], queryFn: () => getActiveInstitute({
instituteId }) }) which does not suspend and can render with activeInstitute
undefined under a Suspense boundary; update the hook to use useSuspenseQuery
with the same queryKey and queryFn (so the Suspense fallback will be shown) or,
if you prefer to keep useQuery, add an explicit loading/error branch that checks
the query state (isLoading/isError/isFetched and activeInstitute) before
rendering the main UI to avoid rendering with undefined activeInstitute; locate
the call to useQuery and either swap it to useSuspenseQuery(...) or add
conditional returns using isLoading/isError and activeInstitute.

try {

const token = await getCookie("token");
const token = await getCookie();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C2 'leadlly\.in_admin_token|getCookie\(|Cookie:' src

Repository: Leadlly/leadlly.admin.web

Length of output: 9443


Fix the forwarded cookie key name to match the authentication middleware.

The code reads the admin cookie correctly via getCookie() which uses the key "leadlly.in_admin_token", but forwards it upstream as Cookie: token=... instead of Cookie: leadlly.in_admin_token=.... The backend proxy and authentication endpoints all expect the full cookie key name "leadlly.in_admin_token", not just "token". This mismatch will cause authentication failures.

The same issue occurs at lines 44-48 and throughout similar action files (batch_actions.ts, institute_actions.ts, mentor_actions.ts, user_actions.ts).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/student_action.ts` at line 18, The forwarded cookie header uses
the wrong key name—getCookie() returns the admin token (from
"leadlly.in_admin_token") but the request header is built as "Cookie: token=..."
causing auth failures; update the code that constructs the Cookie header (where
the local variable token is used) to forward the full cookie key name
"leadlly.in_admin_token" (i.e., change occurrences like `Cookie: token=${token}`
to `Cookie: leadlly.in_admin_token=${token}`) in student_action.ts and the other
action files (batch_actions.ts, institute_actions.ts, mentor_actions.ts,
user_actions.ts) so the backend proxy/auth middleware receives the expected
cookie key; keep using the existing getCookie() call and token variable.

Comment on lines +42 to +60
const handleAddStudents = async (
data: z.infer<typeof AddStudentsFormSchema>
) => {
try {
console.log(data);

const emails = data.emails
.split(",")
.map((email) => email.trim())
.filter((email) => email);

// const result = await addStudentsToInstitute(instituteId, emails);
// if (result.success) {
// toast.success(result.message);
// } else {
// toast.error(result.message);
// }
// form.reset();
// setIsAddStudentsOpen(false);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Wire the submit handler to the server action before shipping this dialog.

Right now submit only parses the textarea and exits. No students are added, no success state is shown, and the dialog never resets/closes.

🚨 Suggested fix
   const handleAddStudents = async (
     data: z.infer<typeof AddStudentsFormSchema>
   ) => {
     try {
-      console.log(data);
-
       const emails = data.emails
         .split(",")
         .map((email) => email.trim())
         .filter((email) => email);
 
-      //   const result = await addStudentsToInstitute(instituteId, emails);
-      //   if (result.success) {
-      //     toast.success(result.message);
-      //   } else {
-      //     toast.error(result.message);
-      //   }
-      //     form.reset();
-      //   setIsAddStudentsOpen(false);
+      const result = await addStudentsToInstitute(instituteId, emails);
+      if (!result.success) {
+        toast.error(result.message);
+        return;
+      }
+
+      toast.success(result.message);
+      form.reset();
+      setIsAddStudentsOpen(false);
     } catch (error) {
       console.error("Error adding students:", error);
       toast.error("Failed to add students. Please try again.");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(protected)/(root)/institute/[instituteId]/_components/add-students-dialog.tsx
around lines 42 - 60, The submit handler handleAddStudents currently only parses
emails and returns; wire it to call the server action
addStudentsToInstitute(instituteId, emails), await the result, and handle
success/error by calling toast.success(result.message) or
toast.error(result.message); on success call form.reset() and
setIsAddStudentsOpen(false). Also wrap the call in the existing try/catch so
errors use toast.error and ensure any loading state (if present) is toggled
appropriately; reference handleAddStudents, addStudentsToInstitute, form.reset,
setIsAddStudentsOpen, toast.success, and toast.error when making the changes.

Comment on lines +5 to +14
await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
}
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Server-side fetch won't forward user cookies; response not validated.

Two issues with this external logout call:

  1. Token not forwarded: credentials: "include" is a browser-context option. In a server-side fetch, cookies from the incoming request are not automatically included. The user's auth token must be manually extracted from req.cookies and forwarded to the external API, otherwise the external service cannot identify which session to invalidate.

  2. Response not checked: If the external logout fails (non-2xx status), the code still clears the local cookie and returns success, leaving the external session active.

🐛 Proposed fix
 export async function GET(req: NextRequest) {
   try {
-    await fetch(
+    const token = req.cookies.get("leadlly.in_admin_token")?.value;
+
+    const response = await fetch(
       `${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
       {
         method: "GET",
         headers: {
           "Content-Type": "application/json",
+          ...(token && { Cookie: `leadlly.in_admin_token=${token}` }),
         },
-        credentials: "include",
       }
     );
+
+    if (!response.ok) {
+      const errorData = await response.json().catch(() => ({}));
+      throw new Error(errorData.message || "Logout failed");
+    }
+
     const res = NextResponse.json({ message: "Logged Out" });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
}
);
export async function GET(req: NextRequest) {
try {
const token = req.cookies.get("leadlly.in_admin_token")?.value;
const response = await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
...(token && { Cookie: `leadlly.in_admin_token=${token}` }),
},
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || "Logout failed");
}
const res = NextResponse.json({ message: "Logged Out" });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/auth/logout/route.ts` around lines 5 - 14, The server-side logout
fetch in src/app/api/auth/logout/route.ts is not forwarding the user's cookie
and isn't checking the external response; update the route handler that calls
fetch(...) so it reads the incoming request's cookie (e.g., from the Request
object via req.headers.get("cookie") or req.cookies) and forwards that token by
adding a Cookie or Authorization header to the external fetch options (replace
credentials:"include" with explicit headers), then inspect the fetch response
(use response.ok / response.status) and only clear the local auth cookie and
return success when the external logout succeeds; if the external call fails,
return an error status (and do not clear the local cookie) and include the
external response details in the error handling.

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

import { getUser } from "./actions/user_actions";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify getUser is a server action
head -5 src/actions/user_actions.ts

Repository: Leadlly/leadlly.admin.web

Length of output: 170


🏁 Script executed:

cat -n src/proxy.ts

Repository: Leadlly/leadlly.admin.web

Length of output: 2109


Server actions cannot be called from Next.js middleware.

getUser() is a server action (from a file with "use server"), but middleware runs in the Edge runtime before the request reaches the server. This will fail at runtime.

Additionally, line 30 accesses user.admin.institutes[0] without null checks on user — if getUser() returns null, this will crash.

Consider:

  1. Making a direct fetch call to your auth endpoint from middleware
  2. Or validating the token using a lightweight JWT verification without calling server actions

Also applies to: 27-27, 35-35

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/proxy.ts` at line 4, Middleware calls the server action getUser (from
"use server") which will fail in the Edge runtime and also accesses
user.admin.institutes[0] without null checks; replace the getUser() invocation
inside the middleware with a direct fetch to your auth endpoint (or perform a
lightweight JWT verify) to obtain user info, and add defensive checks before
accessing nested properties (e.g., confirm user and user.admin exist and that
user.admin.institutes is an array with length > 0) so you never read
user.admin.institutes[0] when user is null/undefined; update the middleware
function where getUser is referenced to use the fetch/JWT flow and guard all
downstream uses of user (including the code path that reads
user.admin.institutes[0]).


import { getUser } from "./actions/user_actions";

export async function proxy(request: NextRequest) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's a middleware.ts file that imports this proxy
fd -e ts -e js 'middleware' --exec cat {}

Repository: Leadlly/leadlly.admin.web

Length of output: 51


🏁 Script executed:

# Check if this is a Next.js project
cat package.json | grep -A 5 '"next"'

Repository: Leadlly/leadlly.admin.web

Length of output: 251


🏁 Script executed:

# Read the proxy.ts file to understand its purpose
cat -n src/proxy.ts

Repository: Leadlly/leadlly.admin.web

Length of output: 2109


🏁 Script executed:

# Search for imports or usages of the proxy function
rg "proxy" --type ts --type js -B 2 -A 2

Repository: Leadlly/leadlly.admin.web

Length of output: 346


🏁 Script executed:

# Check for middleware.ts at project root
ls -la middleware.ts 2>/dev/null || echo "Not found at root"

Repository: Leadlly/leadlly.admin.web

Length of output: 86


🏁 Script executed:

# Check the project structure around src/
ls -la src/ | head -20

Repository: Leadlly/leadlly.admin.web

Length of output: 631


Rename function to middleware and file to middleware.ts per Next.js convention.

Next.js middleware must export a function named middleware from a file named middleware.ts. The current file (src/proxy.ts) and function name (proxy) won't be recognized by Next.js, preventing the authentication logic from executing.

🔧 Proposed fix
  1. Rename file from src/proxy.ts to src/middleware.ts
  2. Rename function from proxy to middleware:
-export async function proxy(request: NextRequest) {
+export async function middleware(request: NextRequest) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function proxy(request: NextRequest) {
export async function middleware(request: NextRequest) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/proxy.ts` at line 6, The exported function named proxy must be renamed to
middleware and the source file renamed to middleware.ts so Next.js will load it
as middleware; update the export signature from export async function
proxy(request: NextRequest) to export async function middleware(request:
NextRequest) and move/rename the file accordingly, ensuring any internal
references or imports that called proxy are updated to middleware.

Comment on lines +594 to +595
phone: string;
password: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue - Sensitive Credentials in Frontend Type Definition: The IAdmin interface includes password and salt fields in a shared types file that's likely imported across both frontend and backend code. This is a critical security vulnerability because:

  1. Password/Salt Should Never Reach Frontend: These fields should NEVER be included in any data sent to the frontend or stored in client-side state (Redux, React state, etc.)
  2. Type Definition Encourages Insecure Usage: Having these fields in the type definition suggests they might be used in frontend code, which would expose user credentials
  3. Related to Previous Issues: This connects to Comments Implemented a fully responsive Admin Dashboard with Batches and Students Page #12-#17 where isAdmin headers are being sent from the client - if admin objects with passwords are being passed to frontend components, this is a severe security breach
    Attack Scenario:
  • If any API endpoint returns admin data matching this interface to the frontend
  • Or if admin data is stored in Redux state (as suggested by the instituteSlice changes in this PR)
  • Attackers can extract password hashes and salts from browser DevTools, network requests, or Redux state
  • These can be used for offline password cracking attacks

Confidence: 5/5

Recommended Action

Create separate type definitions for frontend and backend:

  1. Backend-only type (for database/API operations):
// In a backend-only file (e.g., src/types/backend.ts)
export interface IAdminDB {
firstname: string;
lastname: string;
email: string;
role: "admin" | "super_admin";
phone: string;
password: string;  // Only in backend
salt: string;      // Only in backend
avatar: {
public_id: string;
url: string;
};
permissions: {
manageUsers: boolean;
manageBatches: boolean;
manageClasses: boolean;
manageTeachers: boolean;
manageStudents: boolean;
managePayments: boolean;
manageReports: boolean;
manageSettings: boolean;
manageAdmins: boolean;
};
institutes: string[];
lastLogin: string | null;
status: "Active" | "Inactive" | "Suspended";
activityLog: Array<{
action: string;
timestamp: string;
details?: string;
ipAddress?: string;
}>;
createdAt: string;
updatedAt: string;
}
  1. Frontend-safe type (for UI/Redux):
// In this file (src/helpers/types/index.ts)
export interface IAdmin {
firstname: string;
lastname: string;
email: string;
role: "admin" | "super_admin";
phone: string;
// password and salt REMOVED - never send to frontend
avatar: {
public_id: string;
url: string;
};
permissions: {
manageUsers: boolean;
manageBatches: boolean;
manageClasses: boolean;
manageTeachers: boolean;
manageStudents: boolean;
managePayments: boolean;
manageReports: boolean;
manageSettings: boolean;
manageAdmins: boolean;
};
institutes: string[];
lastLogin: string | null;
status: "Active" | "Inactive" | "Suspended";
createdAt: string;
updatedAt: string;
}

Critical verification needed:

  1. Review ALL API endpoints that return admin data to ensure they NEVER include password or salt fields
  2. Check Redux slices and React components to ensure admin credentials are never stored in frontend state
  3. Implement backend data sanitization to strip sensitive fields before sending to frontend
  4. Add TypeScript utility types to enforce this separation (e.g., Omit<IAdminDB, 'password' | 'salt'>)
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/helpers/types/index.ts at lines 594-595, the IAdmin interface includes password
and salt fields in a shared types file that's used across the application. This is a
critical security vulnerability because these sensitive credential fields should NEVER
be sent to the frontend or stored in client-side state. Remove the password and salt
fields from the IAdmin interface in this file. Create a separate backend-only type
definition (e.g., IAdminDB in src/types/backend.ts) that includes these fields for
database operations only. Review ALL API endpoints that return admin data to ensure
they never include password or salt fields in responses. Check Redux slices and React
components to ensure admin credentials are never stored in frontend state. Implement
backend data sanitization to strip sensitive fields before sending to frontend.

📍 This suggestion applies to lines 594-595


try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/institute/${instituteId}`,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue - Backend API URL Exposure in Server Action: Using NEXT_PUBLIC_ADMIN_API_BASE_URL exposes the internal admin API base URL to all client-side code. Environment variables prefixed with NEXT_PUBLIC_* are embedded in the client-side JavaScript bundle and visible to anyone inspecting browser DevTools or network requests.
Why this matters for server actions: While this is a server action (runs on the server), Next.js bundles server actions into the client-side JavaScript to enable client-side invocation. This means the NEXT_PUBLIC_* variable and the full API endpoint structure become visible in the client bundle.
Security Risks:

  • Attackers can discover the backend admin API endpoint structure
  • If the backend API is meant to be internal/private, this exposure defeats that security boundary
  • Attackers can attempt direct attacks against the exposed backend API endpoints
  • The full API base URL becomes public knowledge, making targeted attacks easier
    This same issue affects:
  • Line 81 in the new getActiveInstitute function
  • Line 110 in the existing getAllUserInstitutes function (not shown in this patch but likely present)
  • Other functions in this file that use the same pattern

Confidence: 5/5

Recommended Action

Use a server-only environment variable instead:
Since this is a Next.js server action, use a server-only environment variable (without the NEXT_PUBLIC_ prefix):

const res = await fetch(
`${process.env.ADMIN_API_BASE_URL}/api/institute/${instituteId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Cookie: `token=${token}`,
isAdmin: "true",
},
credentials: "include",
}
);

Then in your .env file:

ADMIN_API_BASE_URL=https://internal-admin-api.example.com

Benefits:

  • Backend API URL remains server-side only
  • Not exposed in client-side JavaScript bundle
  • Reduces attack surface by hiding internal API structure
  • Follows security best practice of not exposing internal endpoints
    Apply this fix to ALL functions in this file that use NEXT_PUBLIC_ADMIN_API_BASE_URL:
  • createInstitute (line 25)
  • getMyInstitute (line 52)
  • getActiveInstitute (line 81) - this patch
  • getAllUserInstitutes (line 110) - this patch
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/institute_actions.ts at line 81 (and other fetch calls in this file),
the code uses process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL which exposes the internal
admin API base URL to the client-side JavaScript bundle. Even though these are server
actions, Next.js bundles them for client-side invocation, making NEXT_PUBLIC_*
variables visible in the browser. Replace all instances of
process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL with process.env.ADMIN_API_BASE_URL
(without the NEXT_PUBLIC_ prefix) throughout this file. Update your .env file to
define ADMIN_API_BASE_URL with the backend API URL. This prevents exposing internal
API endpoints to the client-side JavaScript bundle and reduces the attack surface.

headers: {
"Content-Type": "application/json",
Cookie: `token=${token}`,
isAdmin: "true",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same critical security issue as Comments #10, #11, and #12 - Adding isAdmin: "true" as a client-controlled request header creates a severe authorization bypass vulnerability if the backend API trusts this header for authorization decisions.
Impact for this endpoint: The /api/student/add/${instituteId} endpoint receives this client-controlled header. If the backend uses this header to determine admin privileges for adding students to institutes, any authenticated user can manipulate this header to gain admin access.
Attack scenario: Attacker modifies the request header to isAdmin: "true" and can add arbitrary students to any institute, bypassing proper authorization checks.

Confidence: 5/5

Suggested Fix
Suggested change
isAdmin: "true",
Cookie: `token=${token}`,

Remove the isAdmin: "true" header. The backend MUST determine admin status from the JWT token validation, not from client-provided headers.
Critical verification required:

  1. Review the backend /api/student/add/${instituteId} endpoint
  2. Ensure it validates admin status from the JWT token, NOT from the isAdmin header
  3. If the header is needed for routing only, document this clearly and ensure backend ignores it for authorization
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/student_action.ts at line 47, a client-controlled header "isAdmin: true"
is being sent to the backend API endpoint /api/student/add/${instituteId}. This is the
same critical security vulnerability identified in Comments #10, #11, and #12 for other
action files. Remove this header from line 47. Review the backend /api/student/add
endpoint to ensure it performs proper server-side authorization by validating admin
status from the JWT token payload, NOT from the isAdmin header. If the backend relies
on this header for authorization, this is a critical vulnerability that must be fixed
immediately by implementing proper server-side JWT validation and role checking.

const { pathname } = request.nextUrl;

const isPublicPath =
pathname === "/" ||
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical Security Issue - Authentication Bypass via Dead Code: Adding pathname === "/" to the isPublicPath check creates an authentication bypass vulnerability. This change makes the root path "/" publicly accessible, but the subsequent authentication check on lines 21-23 becomes unreachable dead code.
Why this is a critical vulnerability:

  1. Line 11 marks "/" as a public path (isPublicPath = true)
  2. Line 17 checks if (!token && !isPublicPath) - this will be FALSE for "/" because isPublicPath is true
  3. Lines 21-23 check if (!token && pathname === "/") - this code is UNREACHABLE because line 17 already handled the !token case for public paths
  4. Result: Unauthenticated users can access "/" without being redirected to login
    Attack scenario: An unauthenticated user navigates to "/" and bypasses authentication because the path is marked as public, while the intended authentication check (lines 21-23) never executes.

Confidence: 5/5

Suggested Fix
Suggested change
pathname === "/" ||
const isPublicPath =
pathname.startsWith("/login") ||
pathname.startsWith("/api/auth/login") ||
pathname.startsWith("/api/google/auth");

Remove pathname === "/" from the public paths list. The root path should NOT be public - it should require authentication. The check on lines 21-23 will then properly execute and redirect unauthenticated users to login.
Alternative approach (if you want "/" to be a landing page):
If the root path should be accessible to both authenticated and unauthenticated users with different behaviors, handle it separately BEFORE the isPublicPath check:

// Handle root path separately
if (pathname === "/") {
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
// Authenticated users continue to lines 27-31
}
const isPublicPath =
pathname.startsWith("/login") ||
pathname.startsWith("/api/auth/login") ||
pathname.startsWith("/api/google/auth");
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/proxy.ts at line 11, adding pathname === "/" to the isPublicPath check creates
a critical authentication bypass vulnerability. This makes the root path publicly
accessible, but the authentication check on lines 21-23 becomes unreachable dead code
because line 17's condition (!token && !isPublicPath) will be false for "/" when
there's no token. Remove pathname === "/" from line 11 so the root path requires
authentication. The check on lines 21-23 will then properly execute and redirect
unauthenticated users to login. If the root path needs special handling for both
authenticated and unauthenticated users, move the pathname === "/" check to a separate
condition BEFORE the isPublicPath logic to ensure proper authentication enforcement.

Comment on lines +27 to +31
const user = await getUser();

return NextResponse.redirect(
new URL(`/institute/${user.admin.institutes[0].toString()}`, request.url)
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Issue - Unhandled Error in Authentication Redirect: The code calls getUser() and accesses user.admin.institutes[0] without error handling. If getUser() fails, returns null, or the admin has no institutes, this will cause an unhandled exception that crashes the middleware.
Potential failure scenarios:

  1. getUser() returns null/undefined (authentication token invalid or expired)
  2. user.admin is undefined (user object structure mismatch)
  3. user.admin.institutes is empty array (admin has no institutes assigned)
  4. user.admin.institutes[0] is undefined (array access out of bounds)
    Impact: Middleware crash causes the entire application to fail for authenticated users trying to access public paths. This creates a denial of service condition where legitimate users cannot access the application.

Confidence: 5/5

Suggested Fix
Suggested change
const user = await getUser();
return NextResponse.redirect(
new URL(`/institute/${user.admin.institutes[0].toString()}`, request.url)
);
if (token && isPublicPath) {
try {
const user = await getUser();
if (!user?.admin?.institutes?.length) {
// Admin has no institutes, redirect to create-institute or error page
return NextResponse.redirect(new URL("/create-institute", request.url));
}
return NextResponse.redirect(
new URL(`/institute/${user.admin.institutes[0].toString()}`, request.url)
);
} catch (error) {
// If getUser fails, clear the invalid token and redirect to login
const response = NextResponse.redirect(new URL("/login", request.url));
response.cookies.delete("leadlly.in_admin_token");
return response;
}

Benefits:

  • Handles getUser() failures gracefully
  • Validates that admin has institutes before accessing array
  • Provides fallback behavior for edge cases
  • Prevents middleware crashes that would affect all users
  • Clears invalid tokens to prevent repeated failures
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/proxy.ts at lines 27-31, the code calls getUser() and accesses
user.admin.institutes[0] without error handling. If getUser() fails, returns null, or
the admin has no institutes, this will cause an unhandled exception that crashes the
middleware, creating a denial of service condition. Wrap lines 27-31 in a try-catch
block. In the try block, validate that user?.admin?.institutes?.length exists before
accessing institutes[0]. If the admin has no institutes, redirect to /create-institute.
In the catch block, clear the invalid authentication token and redirect to /login. This
prevents middleware crashes and provides graceful fallback behavior for edge cases.

📍 This suggestion applies to lines 27-31

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant